101010

プログラミング備忘録とともに、ポエムってます。

SoundPoolを使ってゲームの効果音を再生する | Android Kotlin

f:id:araemonz:20190305182740p:plain

今回はSoundPoolを使ってサウンド再生を行ってみた。 図のように2つのボタンを設置してクリックしたときに効果音が鳴るようなプログラムである。

サウンドファイルはres/rawへ配置する

f:id:araemonz:20190305182735j:plain

図のようにresrawディレクトリを作り、そこに用意したサウンドファイルを配置する。 コード内からファイルを参照するにはR.raw.drumrollのような形でアクセスが可能だ。

参考: リソースの提供

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)
}

参考: Play sound using soundpool example

同時に複数のサウンドを鳴らしたい

maxStreamsに1以上の数を指定してあげることで、その数分だけ同時再生が可能になる。maxStreamsを大きくすればそれだけ処理に負荷がかかるので、必要以上に値を大きくすべきではないだろう。

soundPool = SoundPool.Builder().apply {
    setMaxStreams(2)
    setAudioAttributes(attributes)
}.build()

参考: Playing multiple SoundPool at the same time

音楽ファイルはsoundIDで管理する

SoundPoolloadメソッドで音声データをロードするとint型のsoundIDが返ってくる。このsoundIDplayメソッドで再生するときに必要になるのでメンバ変数に保存しておく。

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)
    }
}

companion objectsoundIDのメンバ変数を定義すると、Activityから次のような形で呼び出せるので便利だ。

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)
        }

    }

}

SoundPool生成のタイミングの問題で、初回のみ再生出来ない現象があるのでSound.getInstance(this)に一度触れて事前に初期化している。

Sound.getInstance(this)

この問題はこちらでも議論になっているので参考に。

Why does my SoundPool sound not play the first time on Android?