jaadとJavaのbit演算とCのbit演算と多分、バグ...

Androidアプリの開発で各種エンコーダが必要でAACファイルから取り掛かった。

AACファイルはいろいろ面倒で、手持ちのハードウェアならOS2.1なのでMediaPlayerクラスを使えば再生できるのだけど、逆にOS2.1が故にイコライザとかがそのまま手軽には実装できない。iTunesライブラリの中身はMP3とAACとApple Losslessだけだし、iTunesにしがみつく?人もその3つ実装しとけば問題ないだろうということで作業を始めた。

で、AACのデコーダだと普通はFAAD2なるものが有名だし、それを使う。がAndroidだと使えるかわからないし、ndkになってしまうので、jaadというのを使うことにした。βバージョンだが全部Java。Java Soundに依存していたり、動画用の実装も含まれているが、不要な部分は削除してしまえばいい。要するに、AAC→PCMのバイト列が得られればいいのだ。

ということで動かしたけどコンパイルエラーは除去できたけど動かない。どう見てもおかしい。

で、ソースを覗く。で、一番最初にファイルを読み込んでヘッダ情報を取得する部分でいきなり気になる部分が見つかった。


int i;
i = in.read();
if(i==0xFF) {
  i = in.read();
  if(((i>>4)&0xF)==0xF) found = true;
  in.unread(i);
}

というコードがある。
これ、多分、Cから直接移植した部分だと思われる。
想定される動作は、ストリームから1バイト読み込む。
格納するのはint型。これはライブラリの仕様。
で、読み込んだ値が0xFF(10進の255)の時は次の1バイトを読み込んで判定する。
次のバイトを右に4ビットシフトして0xFとANDを取って0xFと同じ時、すなわち、シフトした値が0xFだったときに処理をするということだ。
問題はこの「4ビット右シフト」の仕様だ。
右シフトは基本的に割り算と同じと考えてよい。1ビット右シフトは2で割るのと等価。ここでは4ビットシフトしているから2の4乗で16で割っていることになる。正数だけのときはこれでよいのだ。
問題は、元が1バイトで、それをint型で受けているにもかかわらず、符号を考慮していないこと。
1バイトで表現できる整数型を符号付きで考えた場合は、-128≦i≦127(10進数の時)という範囲になる。上限は16進なら0x7Fだ。最上位ビットは0である。
逆に考えると、算術右シフトを行っているため、符号が保存されることを考えると、上記ソースが期待しているのは、「負数の時にif文が真になってほしい」ということだと予想される。
しかし、Javaでこのソースをデバッガで追いかけるとそうは判定されない。
ストリームから読み込んでint型で受け取った時点ですべての値は0≦i≦255に格納されているため、負の数は存在しない。そのため、算術右シフト演算を行った場合、空いた上位ビットには符号ビット「ゼロ」が埋められる。
結果として、上記ソースの条件式が「真になる」のは0xF0≦i≦0xFFという極めて狭い範囲に限られてしまう。シフト演算する必要もなく目で見てわかるというか、0xF0とANDを取っても同じというかそんなところだ。

結構、このプロジェクトが動いているのは履歴を見るとわかるのだが、ソースを読むとこの手のシフト演算がやたらと多い。その中のどれだけが書いた人間の意図したとおりに動くのかは全くわからない。開発者たちが実際に音楽ファイルを再生しているのかもわからない。

とりあえず、Android向けに開発するのをやめて、Windowsアプリとしてこのライブラリを動くようにしてからにしないと時間がかかりすぎるような気がする。エミュレータは重いし。

が、果たして、jaadを修正するのが速いのか、FAAD2を自力で移植するのが速いのか、別の方法(ndkでAndroidのオーディオを叩くライブラリもどこかで見つけた)を使うのがいいのかわからなくなってきた。そもそも、AACにもいろいろな種類があるので、iTunesで作ったAACファイルがjaadでデコードできる保証がない(公式サイトにはLow Complexity未対応と書いてあったのに、source fourge.jpには対応となっていた)。どうしよう。

コメント