メモ用紙の裏 このページをアンテナに追加 RSSフィード

ここに書いてあるより詳しい話はこっちにあるかもしれません(特にプログラム関係)。

2009-09-03

JavaSound on Linux

| 01:42

ドハマリしたので記録。

事の発端はJavaSoundで音を出して遊ぼうとしたこと。

AudioFormat format = new AudioFormat(44100, 16, 2, true, false);
SourceDataLine line = AudioSystem.getSourceDataLine(format);
line.open()

こんな感じでSourceDataLineを取得して、lineに適当に波形流し込んで遊ぼうとしてたんですが、どうも時々line.open()でLineUnavailableExceptionが投げられる。で、色々調べた結果、JavaSoundはALSAではなくOSS向けの実装になってるぽくて、/dev/dsp排他的に開く様になっていて、

% lsof /dev/dsp
COMMAND  PID USER   FD   TYPE DEVICE SIZE NODE NAME
java    3544  osa    9w   CHR   14,3      8592 /dev/dsp

こんな事になる。これがFlashプラグインと衝突していて、先にfirefoxFlashを見ていると(Flashプラグインは一度起動されるとFlashの実行が終わっても生き残り続け、サウンドデバイスを開きっぱなしにするため)LineUnavailableException: Audio Device Unavailableになる様です。

で、この現象について調べていたらこんなページが引っかかる。これによるとJavaSoundはplughw:0,0を掴もうとするので.asoundrcでplughwをdmix*1すればいいよ、と言うことですが、大嘘です。

実際にAudioSystem.getMixerInfoでMixerを取得すると、自分の環境では次の3つを得ます。

SB [plughw:0,0], version 1.0.18a:Direct Audio Device: HDA ATI SB, AD198x Analog, AD198x Analog
Java Sound Audio Engine, version 1.0:Software mixer and synthesizer
Port SB [hw:0], version 1.0.18a:HDA ATI SB, Analog Devices AD1981

このうち最後のものはよく分からんのですが、一番上と二番目はSourceDataLineを返せるMixerで、しかも一番上はいろいろなフォーマットに対応している様子を見せます(この辺は自力でプログラムを組むよりjsinfoを使った方が楽)。

が、しかし!コイツから得たSourceDataLineはopen()できない!どんなAudioFormatを渡しても(自分で対応してると言ってるフォーマットですら)「それはサポートしてないよ」と言われるだけ。結局使えるのは二番目のMixerが返すものだけで、これが前述のOSSで/dev/dspを掴んでしまうLineです。.asoundrcに色々書いて試してみるも、全然状況は変わらず。詰んだ。

で、ここから色々と探していると、こんなスレッドを発見。reply 2 of 4の人が、前のplughw:0,0をどうのと言っていたサイトについて、アレはたまたまサウンドカードの相性が合っていただけぽいよという旨の発言をしています。自分はあのページでは出来ているんだからこっちの環境が悪いんじゃないかと思って四苦八苦してたんだけど・・・。

で、ここで気分を変えて、調査途中で見かけたPulseAudioについて調べる。これもdmixとやることは同じみたいだけど、PulseAudioというドライバALSAの前のレイヤーとして挟まるらしい。それくらいALSAなら自力で出来んじゃない?と思って「alsa dmix dsp」で検索すると・・・出た!aossというコマンドを使うといいらしい。ちなみにこれは単なるシェルスクリプトで、LD_PRELOADにlibaoss.soを入れるだけのもの。これを使ってaoss経由でjavaコマンドを実行したら共存した・・・!長かった・・・!

plughwの話がなければもっと早くたどり着けていた気がする。というかJavaSound on Linuxの情報少なすぎだよ・・・!英語でも「うまく動かん!どうすればいいの!→JavaSoundの実装がゴミなんだよ」位の流れしか無い。

ちなみに、aossOSSALSAの皮を被せて排他ロックしないようにするだけなので、dmixは別途設定が必要。自分の.asoundrcは最終的にこんなの。色々調べては導入して消してを繰り替えした割にはあっさりとまとまった。

pcm.!default {
	type plug
	slave.pcm "dmixer"
}

#pcm.!plughw {
#	type plug
#	slave.pcm "dmixer"
#}

#pcm.dsp {
#	type plug
#	slave.pcm "dmixer"
#}

#pcm.dsp0 {
#	type plug
#	slave.pcm "dmixer"
#}

ctl.mixer0 {
	type hw
	card 0
}

pcm.dmixer {
	type dmix
	ipc_key 1024
	slave {
		pcm "hw:0,0"
		period_time 0
		buffer_time 0
		period_size 1024
		buffer_size 8192
		format S32_LE
		channels 2
		rate 44100
	}
	bindings {
		0 0
		1 1
	}
}
ctl.dmixer {
	type hw
	card 0
}

*1:複数のソフトウェアからのサウンド入力をALSAがmixする機能

2009-08-25

エンディアン爆発しろ

| 20:58

Java Sound APIで音を出すプログラム書いてたんですが、なんか音がおかしいなーと思ってたらshort配列→byte配列の変換時にエンディアンの扱いをミスってただけでした。

こういうバグの温床は根絶すべき。具体的には1byte=32bit位になるとcharもshortもintも全く同等でエンディアンとかいらなくなって、さらに確実に1文字1charが徹底できるという副作用もあって嬉しいことずくめだと思うよ!

2009-08-20

Java Generics

| 12:31

久々にJavaで開発中。5.0になって以降全然触れてなかったので、Genericsとかもちまちまと触れながらやってます。

で、このGenerics。C++のテンプレートみたいなのかと思ったら全然違うのね。今やりたいのはファイルから特定の構造を読み込むということで、そのためにFilterInputStreamを拡張してgetIntとかgetStringとかぐだぐだ作ろうかと思ったんだけど、Little endianとBig Endianの違いにも対処しなくちゃいけなくて、この方法で行くと同じエンディアン変換コードが至る所にコピーされて美しくないなぁと思って、そうだGenericsってこういうとき使えんじゃね、と思ったわけです。

具体的にはこんなことをしたい。

//欲しいコード in C++
//int i = getPrimitive<int>(fp, false);
//short s = getPrimitive<short>(fp, true);
//のように使う。
template<class T> T getPrimitive(FILE *fp, bool littleEndian) {
    unsigned char *buf = new unsigned char[sizeof(T)];
    fread(buf, sizeof(unsigned char), sizeof(T), fp);
    T result = 0;
    for(int i = 0; i < sizeof(T); i++) {
        result |= littleEndian ? (buf[i]<<(8*i)) : (buf[i]<<(8*(sizeof(T)-i-1)));
    }
    delete[] buf;

    return result;
}

しかし、Javaにはsizeofが無いのでこの手法が使えません。それならD言語みたいにtypeid(typeof(T))でif文に入れればいいかと思ったけど、typeofが無いので無理。妥協して引数にサイズを渡して読み込むのも考えたけど、返り値は固定になるので無理。サイズによってプリミティブのWrapperクラスを生成して返して、呼び出し元で(Integer)とかキャストするとUnboxingがあるのでvalueOfは書かなくても済むけど、中の分岐が・・・。

ここまで書いて気づいた。こうすればいいんじゃね?

//in Java
//int i = (Integer)getPrimitive(in, 4, true);
//short s = (Short)getPrimitive(in, 2, false);
//こう使う。UnboxingによってWrapper->Primitiveは自動的に変換される。
Object getPrimitive(InputStream in, int size, boolean littleEndian) {
    byte[] buf = new byte[size];
    in.read(buf, 0, size);
    long result = 0;
    for(int i = 0; i < size; i++) {
        result |= littleEndian ? (buf[i]<<(8*i)) : (buf[i]<<(8*(size-i-1)));
    }
    switch(size) {
        case 2:
            return new Short((short)result);
        case 4:
            return new Integer((int)result);
        case 8:
            return new Long(result);
    }

    throw new IllegalArgumentException("Invalid size.");
}

あれ、これって符号どうなるんだっけ。時間ないので後で試す。

試した。キャストは単純に下位nバイトを取るだけみたいね。この実装の美しくないところは、型をキャストとsizeで2回表現してるところ。特にsizeはMagic Numberになるので見た目もよろしくなさげ。しかし1回の解は思いつかない。

考えられる妥協解は、文字列をテンプレートにしてunpackを実装することか?テンプレートで1回目、取り出すときのキャストで2回目の型表明があるけど、sizeを与えるよりは美しく見える。

StopStop 2009/08/20 12:23 お金の使い方を最適化するAI

osa_kosa_k 2009/08/20 23:13 AIの言う通りに事が進めば誰も困らないんです・・・!