简体   繁体   中英

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.

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.

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

}

You're passing in byte length of the stream. setLoopPoints() expects the number of audio blocks in the stream. See getBufferSizeInFrames() in the documentation.

However. I caution you that most particular audio formats are not block based; they are sample based. MP3 is the sole exception; it is aligned on 1152 byte boundaries. If the source content is MP3 and not explicitly authored to be block aligned, seamless looping is impossible. You seem to be using "whitenoise.wav" as your audio source. Unless the length of this file is explicitly aligned to block size, you may have audio artifacts when looping.

The workaround for this is to cross-fade the beginning and final frame blocks when you loop, and handle all the buffering yourself, but you'd have to write code to do this yourself.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM