Hatena::ブログ(Diary)

Hacking My Way 〜 itogのhack日記 RSSフィード Twitter

2010-09-27

SoundPool使用上の注意点

SoundPoolはAndroid APIに含まれる音声再生ライブラリのひとつで、ファイルをロード時にデコードしておくので、再生時にほとんど遅延が生じないため、効果音の再生などに向いている。

典型的な使い方

onCreate()などの初期化処理でファイルをload()しておき、効果音を鳴らしたいイベントが発生したときにplay()する。

public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);

	context = this.getApplicationContext();

	soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
	soundIds = new int[soundResouces.length];
	for (int i=0; i<soundResouces.length; i++) {
		soundIds[i] = soundPool.load(context, soundResouces[i], 1);
	}
	playButton = (Button)findViewById(R.id.PlayButton);
	playButton.setOnClickListener(new OnClickListener() {
		public void onClick(View v) {
			if (index >= soundIds.length) {
				index = 0;
			}
			Log.v(TAG, "index = " + index);
			soundPool.play(soundIds[index++], 1.0f, 1.0f, 1, 0, 1);
		}        	
	});
}

使用上の注意

デコードに時間がかかる

load()は非同期ですぐに返ってくるが、デコードがネイティブのスレッドで動作している。

デコードが終わったかどうか分からないのですぐload()直後にplay()を呼ぶと、デコードが終わってなく再生されない時がある

public void onClick(View v) {
	int soundId = soundPool.load(context, R.raw.bashi2, 1);
	soundPool.play(soundId, 1.0f, 1.0f, 1, 0, 1);
}

再生されなかった場合、LogCatにwarningがでる。

WARN/SoundPool(32232): sample 11 not READY

API Level 8からはロードの終了通知を受け取れるようになった。

public void setOnLoadCompleteListener (SoundPool.OnLoadCompleteListener listener)

256個の壁

load()で返ってくるidは0からインクリメントされていくが、ネイティブでのメモリ管理領域の都合上idの数は256個まで。257個めをload()しようとするとエラーになる。

ERROR/SoundPool(2693): Unable to load sample: (null)

unload()してもload()で取得できるidの再利用はされない。idをクリアするには

soundPool.release()

をして一度全部まっさらな状態にするしかない。

10秒の壁

SoundPoolで再生出来るのは10秒までっぽい。

HW依存のような気もするが、NexusOne/GDD/Emulatorのどれでも10秒以上だと途切れてしまうのでSW的な制限があるのかもしれない。

また、エンコードはoggにしておくのが無難

(Thank @tomorrowkey @hyoromo)

ネイティブヒープの圧迫

load()された音声データはデコードされた状態でメモリ上に保存されるので、メモリをかなり消費する。

しかもjavaのヒープではなく、ネイティブのヒープとられるので、onLowMemory()が来たりせずに固まったり、落ちたりする。

大きいファイルをload()しようとすると

ERROR/AudioCache(51): Heap size overflow! req size: 1052672, max size: 1048576

というLogがでるが、java側には通知されず、処理もそのまま走ってる様子。

Native側のメモリの状況なんて端末スペックやシステムの状態で全然変わってくるので、できるだけload()するファイルの総量は小さくして、スペックの低い端末で動作確認するくらいしかなさそう。


動作確認したときのコードをgithubに上げました。

あわせて読みたいブログパーツ テクノラティのお気に入りに追加する