Android AudioTrack with setLoopPoints cuts out early

I am writing a simple android compose app in which I want to do some basic synthesized sounds. My starting problem is getting a sine wave to repeat forever. For now, I am trying to get AudioTrack, although I believe a more modern approach would be to use the Oboe library.

Here is what I have:

fun generateSound(sampleRate: Int, timeInHz: Int = 44100, noCycles: Int = 1) : ShortArray {
// Not the most efficient function, but easy to read.
// Note that timeInHz is in Hz => 44100/44100 = 1000 ms

    val mSingleCycle = ShortArray(timeInHz)
    var mSound = ShortArray(timeInHz*noCycles)

    // Single cycle
    for (i in 0 until timeInHz) {
        mSingleCycle[i] = (sin(2.0*PI * 440.0*i/sampleRate.toDouble())*Short.MAX_VALUE).toInt().toShort()
    }

    // Multiple cycles
    for (i in 0 until timeInHz*noCycles) {
        mSound[i] = mSingleCycle[i % timeInHz]
    }

    return mSound
}

suspend fun playSound(isRunning: Boolean) {

    val sampleRate = 44100
    val mSound = generateSound(sampleRate, 44100, 4)

    // Set up AudioTrack
    val mAudioTrack = AudioTrack(AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
        .build(),
        AudioFormat.Builder()
        // We use pcm_16bit => pass Short arrays
            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
            .setSampleRate(sampleRate)
            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build(),
        // mSoundSize is size of short array. A short is 2 bytes,
        mSoundSize*2,
        AudioTrack.MODE_STATIC,
        AudioManager.AUDIO_SESSION_ID_GENERATE,
    )

    if (mAudioTrack.playState == AudioTrack.PLAYSTATE_PLAYING) {
        Log.d(LOG_TAG,"Was playing. Stopping and reloading.")
        mAudioTrack.stop()
        mAudioTrack.reloadStaticData()
    }

    val bytesWritten = mAudioTrack.write(mSound, 0, mSound.size, AudioTrack.WRITE_NON_BLOCKING)
    mAudioTrack.setLoopPoints(0,mSound.size-1,-1)
    mAudioTrack.play()

    // The following is for debugging. If removed, don't need this function to suspend.
    for (i in 0..49) {
        Log.d(LOG_TAG, "playbackHead = ${mAudioTrack.playbackHeadPosition}")
        delay(500)
    }
}

And I call playSound from a LaunchedEffect.

In summary, I am making AudioTrack play a (4 second) sound and using the setLoopPoints method to make this into an infinite loop.

The problem I have is the sound plays for around 10 or 20 cycles but then cuts out entirely. Can someone explain why? What can I do to make it play ‘forever’?

A curious thing I notice is that if I insert the debugging code just above (with the delay etc) it does not cut out until all the delays have been done. It seems like the AudioTrack has a timeout when it hasn’t been used in a while – is this correct?

As a bonus question, how would accomplish the same idea but using the Oboe library (but maybe that constitutes another question!)?

Leave a Comment