Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play looping audio using AudioTrack

I have a feature I want to transition over to use Androids AudioTrack instead of MediaPlayer, due to a few well known bugs with MediaPlayer, such as the small gap that appears between looping tracks.

I've been recommended to use AudioTrack but haven't found to many examples of it in use. I did find a question on SO regarding AudioTrack and used some of that code to hack together something:

public class TestActivity extends Activity implements Runnable {

    Button playButton;
    byte[] byteData = null;
    int bufSize;
    AudioTrack myAT = null;
    Thread playThread = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        playButton = (Button) findViewById(R.id.testButton);

        InputStream inputStream = getResources().openRawResource(R.raw.whitenoise_wav);
        try {
            byteData = new byte[ inputStream.available()];
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            inputStream.read(byteData);
            inputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        initialize();

        playButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {

                playThread.start();
            }
        });
    }

    void initialize() {

        bufSize = android.media.AudioTrack.getMinBufferSize(44100,
                AudioFormat.CHANNEL_CONFIGURATION_STEREO,
                AudioFormat.ENCODING_PCM_16BIT);

        myAT = new AudioTrack(AudioManager.STREAM_MUSIC,
                44100, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
                AudioFormat.ENCODING_PCM_16BIT, bufSize,
                AudioTrack.MODE_STREAM);
        myAT.setVolume(.2f);

        playThread = new Thread(this);
    }

    public void run() {
        if (myAT != null) {
            myAT.play();
            myAT.setLoopPoints(0, byteData.length, 6);
            myAT.write(byteData, 0, byteData.length);
        }
    }
}

So this does seem to play the entire audio track (~1:00 min) and then stops. Now the end goal here is two have 2 seperate audio tracks playing and looping at the same time. I currently have the audio tracks in the /res/raw/ directory, but I can move them to a simple assets folder if that would be better. Is my current implementation of AudioTrack correct? If so, how would I get it to loop?

In summation: how can you play looping audio without a gap using AudioTrack?

Suggestions for alternative ways to get looping audio, such as third party libraries, are welcomed.

like image 604
Orbit Avatar asked Mar 24 '16 01:03

Orbit


2 Answers

You can't loop using an AudioTrack configured with AudioTrack.MODE_STREAM. If you use MODE_STREAM AudioTrack needs to be filled with new samples continuously.

But you can configure it with AudioTrack.MODE_STATIC and pass the entire buffer to be played (I mean: if you need to mix two samples, you have to pass the mixed samples).

setLoopPoints: Sets the loop points and the loop count. The loop can be infinite. Similarly to setPlaybackHeadPosition, the track must be stopped or paused for the loop points to be changed, and must use the MODE_STATIC mode.

Please note that AudioTrack plays raw PCM samples, there's no support for WAV, MP3 or other containers.

like image 142
Mimmo Grottoli Avatar answered Nov 20 '22 07:11

Mimmo Grottoli


Look at this example, it seems a similar issue, solved feeding continuously the AudioTrack.

class ToneGenerator {
    int sampleRate = 8000;
    double sample[] = null;
    byte generatedSnd[] = null;
    int m_ifreq = 400;
    Thread m_PlayThread = null;
    boolean m_bStop = false;
    AudioTrack m_audioTrack = null;
    int m_play_length = 1000;//in seconds

    static public void PlayTone(int freq, int play_length) {
        ToneGenerator player = new ToneGenerator();
        player.m_ifreq = freq;
        player.m_play_length = play_length;
        player.play();
    }

    synchronized void stop() {
        m_bStop = true;
        if (m_PlayThread != null) {
            try {
                m_PlayThread.interrupt();
                m_PlayThread.join();
                m_PlayThread = null;
            } catch (Exception e) {

            }
        }
        if (m_audioTrack != null) {
            m_audioTrack.stop();
            m_audioTrack.release();
            m_audioTrack = null;
        }
    }

    synchronized void play() {
        m_bStop = false;
        m_PlayThread = new Thread() {
            public void run() {
                try {
                    int iToneStep = 0;

                    m_audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                            sampleRate, AudioFormat.CHANNEL_OUT_MONO,
                            AudioFormat.ENCODING_PCM_16BIT, 2 * sampleRate,
                            AudioTrack.MODE_STREAM);

                    while (!m_bStop && m_play_length-- > 0) {
                        genTone(iToneStep++);

                        m_audioTrack.write(generatedSnd, 0, generatedSnd.length);
                        if (iToneStep == 1) {
                            m_audioTrack.play();
                        }
                    }
                } catch (Exception e) {
                    Log.e("Tone", e.toString());
                } catch (OutOfMemoryError e) {
                    Log.e("Tone", e.toString());
                }

            }
        };
        m_PlayThread.start();
    }

    //Generate tone data for 1 seconds
    synchronized void genTone(int iStep) {
        sample = new double[sampleRate];

        for (int i = 0; i < sampleRate; ++i) {
            sample[i] = Math.sin(2 * Math.PI * (i + iStep * sampleRate) / (sampleRate / m_ifreq));
        }

        // convert to 16 bit pcm sound array
        // assumes the sample buffer is normalised.
        generatedSnd = new byte[2 * sampleRate];
        int idx = 0;
        for (final double dVal : sample) {
            // scale to maximum amplitude
            final short val = (short) ((dVal * 32767));
            // in 16 bit wav PCM, first byte is the low order byte
            generatedSnd[idx++] = (byte) (val & 0x00ff);
            generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
        }
    }

}
like image 32
loretoparisi Avatar answered Nov 20 '22 07:11

loretoparisi