【Kotlin】SoundPoolでゲームの効果音を再生する【Androidアプリ開発】
今回のAndroidサンプルプロジェクトでは、SoundPoolを使ってサウンド再生をやってみた。図のように2つのボタンを設置して、クリックしたときに効果音が鳴るだけのシンプルなプログラムである。
サウンドファイルはres/rawへ配置する
図のようにresにrawディレクトリを作り、そこに用意したサウンドファイルを配置する。コード内からファイルを参照するにはR.raw.drumrollの形でアクセスが可能だ。
参考
アプリリソースの概要 - Androidデベロッパードキュメントガイド
SoundPoolをシングルトンで管理する
複数Activityをまたがった時にサウンドが途切れないようにしたいので、次のようにシングルトンで実装してみた。
companion object {
var SOUND_DRUMROLL = 0
var SAD_TROMBONE = 0
var INSTANCE:Sound? = null
fun getInstance(context: Context) =
INSTANCE ?: Sound(context).also {
INSTANCE = it
}
}
LOLLIPOP以降はBuilderで生成する
LOLLIPOP以前と以降ではSoundPoolの生成方法が違うので注意が必要だ。LOLLIPOP以降ではコンストラクタが使えなくなっておりBuilderで生成することになっている。次のようにSDKのバージョンを比較して条件分岐することになる。
private fun createSoundPool() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
createNewSoundPool()
} else {
createOldSoundPool()
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun createNewSoundPool() {
val attributes = AudioAttributes.Builder().apply {
setUsage(AudioAttributes.USAGE_GAME)
setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
}.build()
soundPool = SoundPool.Builder().apply {
setMaxStreams(2)
setAudioAttributes(attributes)
}.build()
}
private fun createOldSoundPool() {
soundPool = SoundPool(2, AudioManager.STREAM_MUSIC, 0)
}
参考
android-Play sound using sound pool example-StackOverflow
同時に複数のサウンドを鳴らしたい
maxStreamsに1以上の数を指定してあげることで、その数分だけ同時再生が可能になるmaxStreamsを大きくすればそれだけ処理に負荷がかかるので、必要以上に値を大きくすべきではないだろう。soundPool = SoundPool.Builder().apply {
setMaxStreams(2)
setAudioAttributes(attributes)
}.build()
参考
android-Playing multiple Sound Pool at the same time-StackOverflow
音楽ファイルはsoundIDで管理する
SoundPoolのloadメソッドで音声データをロードするとint型のsoundIDが返ってくる。このsoundIDはplayメソッドで再生するときに必要になるのでメンバ変数に保存しておく。private fun loadSoundIDs(context:Context) {
soundPool?.let {
println("サウンドファイルロード")
SOUND_DRUMROLL = it.load(context, R.raw.drumroll, 1)
SAD_TROMBONE = it.load(context, R.raw.sad_trombone, 1)
}
}
Sound.getInstance(this).playSound(Sound.SOUND_DRUMROLL)
Soundクラスの全容
以上の内容で作ったSoundクラスの全容を載せておく。
class Sound constructor(context:Context) {
private var soundPool: SoundPool? = null
companion object {
var SOUND_DRUMROLL = 0
var SAD_TROMBONE = 0
var INSTANCE:Sound? = null
fun getInstance(context: Context) =
INSTANCE ?: Sound(context).also {
INSTANCE = it
}
}
init {
createSoundPool()
loadSoundIDs(context)
}
private fun createSoundPool() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
createNewSoundPool()
} else {
createOldSoundPool()
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun createNewSoundPool() {
val attributes = AudioAttributes.Builder().apply {
setUsage(AudioAttributes.USAGE_GAME)
setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
}.build()
soundPool = SoundPool.Builder().apply {
setMaxStreams(2)
setAudioAttributes(attributes)
}.build()
}
private fun createOldSoundPool() {
soundPool = SoundPool(2, AudioManager.STREAM_MUSIC, 0)
}
private fun loadSoundIDs(context:Context) {
soundPool?.let {
println("サウンドファイルロード")
SOUND_DRUMROLL = it.load(context, R.raw.drumroll, 1)
SAD_TROMBONE = it.load(context, R.raw.sad_trombone, 1)
}
}
fun playSound(soundID:Int) {
soundPool?.let{
it.play(soundID, 1.0f, 1.0f, 1, 0, 1.0f)
println("サウンド再生")
}
}
fun close() { // シングルトンの場合呼びようがない?
soundPool?.release()
soundPool = null
}
}
MainActivityからサウンド再生する
最後にMainActivityからSoundクラスのインスタンスを生成してサウンドを鳴らしてみよう。
class MainActivity : AppCompatActivity() {
private lateinit var sound:Sound
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初回のみ再生出来ないのでgetInstanceに触れて初期化しておく
Sound.getInstance(this)
findViewById<Button>(R.id.sound1).setOnClickListener {
Sound.getInstance(this).playSound(Sound.SOUND_DRUMROLL)
}
findViewById<Button>(R.id.sound2).setOnClickListener {
Sound.getInstance(this).playSound(Sound.SAD_TROMBONE)
}
}
}
Why does my Sound Pool sound not play the first time on Android?-StackOverflow
SoundPoolのバグ?
SoundPoolを鳴らしていると、そのうち鳴らなくなったりする現象を発見した。その時コンソールには次のエラーが表示された。
E/AudioTrack: AudioFlinger could not create track, status: -12
E/SoundPool: Error creating AudioTrack
ググってみるとこちらの記事が該当するようだ。
記事とは違って、シングルトンの場合どうしたら良いかわからない。一定時間または一定回数鳴らしたら強制的にインスタンスを再生成するとか?
AudioTrackはシングルトンにした方が安定する。ちなみに、wavからoggファイルへ変換したところSoundPoolの不安定さがなくなった。wavからoggへ変換するにはffmpegで可能。
10個分のwavとoggファイルの合計サイズを比較してみた。oggはwavの1/10程度まで圧縮されている。効果音に使う分には音質は気にならない。oggがなかなか良さげだ。
212K ./ogg
2.3M ./wav
▼ こんな記事も書いてます。