The preface

When it comes to playing audio on Android, the first thing that comes to mind is MediaPlayer, which is fully encapsulated by the API and can be played with a small amount of code. MediaPlayer can play sound files in a variety of formats such as MP3, AAC, WAV, OGG, MIDI, etc. AudioTrack can only play PCM data streams.

In fact, when MediaPlayer plays the audio, it still creates an AudioTrack in the Framework and passes the decoded PCM number to AudioTrack. Finally, AudioFlinger mixes the audio and passes it to the hardware for playback. Playing with AudioTrack just skips the decoding part of Mediaplayer. If it is real-time audio data, it can only be played by AudioTrack.

AudioTrack has two data loading modes (MODE_STREAM and MODE_STATIC), which correspond to two completely different usage scenarios.

  • MODE_STREAM: In this mode, audio data is written to AudioTrack again and again by write. This is similar to writing data to a file through a write call, but in this way, data needs to be copied from the user-provided Buffer to the internal Buffer of AudioTrack each time, which causes delay to some extent. To solve this problem, AudioTrack introduced a second mode.
  • MODE_STATIC: In this mode, all data is passed to the internal buffer in the AudioTrack with a single write call before play, and no further data is passed. This mode is suitable for files with small memory footprint and high latency requirements such as ringtones. One drawback is that you can’t write too much data at a time, or the system won’t be able to allocate enough memory to store all the data.

In the AudioTrack constructor, the audiomanager.stream_music parameter is touched. Its meaning has to do with how the Android system manages and classifies audio streams. Android classifies system sounds into several stream types. Here are a few common ones:

  • STREAM_ALARM: warning sound
  • STREAM_MUSIC: music sounds, such as music
  • STREAM_RING: the bell
  • STREAM_SYSTEM: system sound, such as low tone, screen lock sound, etc
  • STREAM_VOICE_CALL: call sound

Note: These types of partitioning have nothing to do with the audio data itself. For example, the MUSIC and RING types can both be an MP3 song. In addition, there is no set standard for the selection of sound stream type. For example, the ringtone in ringtone preview can be set to MUSIC type. The classification of Audio stream types is related to the Audio management strategy of the Audio system.

Let’s practice the flow of playback in code

  1. Create a playback object with parameters similar to AudioRecord.
    public void createAudioTrack(String filePath) {
        mFilePath = filePath;
        mBufferSizeInBytes = AudioTrack.getMinBufferSize(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING);
        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING,
                mBufferSizeInBytes, AudioTrack.MODE_STREAM);
        mStatus = Status.STATUS_READY;
    }

Copy the code
  1. Start playing, constantly reading data from the file, and then writing data to Buffer.
    public void start() {
        if (mStatus == Status.STATUS_NO_READY || mAudioTrack == null) {
            throw new IllegalStateException("Player has not been initialized");
        }
        if (mStatus == Status.STATUS_START) {
            throw new IllegalStateException("Playing...");
        }
        Log.d(TAG, "===start===");
        mExecutorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    writeAudioData();
                } catch (IOException e) {
                    Log.e(TAG, e.getMessage());
                    mMainHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(mContext, "Playback error", Toast.LENGTH_SHORT).show(); }}); }}}); mStatus = Status.STATUS_START; } private void writeAudioData() throws IOException { DataInputStream dis = null; try { mMainHandler.post(newRunnable() {
                @Override
                public void run() {
                    Toast.makeText(mContext, "Start playing", Toast.LENGTH_SHORT).show(); }}); FileInputStream fis = new FileInputStream(mFilePath); dis = new DataInputStream(new BufferedInputStream(fis)); byte[] bytes = new byte[mBufferSizeInBytes]; int len; mAudioTrack.play();while((len = dis.read(bytes)) ! = -1 && mStatus == Status.STATUS_START) { mAudioTrack.write(bytes, 0, len); } mMainHandler.post(newRunnable() {
                @Override
                public void run() {
                    Toast.makeText(mContext, "End of playback", Toast.LENGTH_SHORT).show(); }}); } finally {if(dis ! = null) { dis.close(); }}}Copy the code
  1. Stop playing and release resources.
    public void stop() {
        Log.d(TAG, "===stop===");
        if (mStatus == Status.STATUS_NO_READY || mStatus == Status.STATUS_READY) {
            throw new IllegalStateException("Play has not started yet");
        } else {
            mAudioTrack.stop();
            mStatus = Status.STATUS_STOP;
            release();
        }
    }

    public void release() {
        Log.d(TAG, "==release===");
        if(mAudioTrack ! = null) { mAudioTrack.release(); mAudioTrack = null; } mStatus = Status.STATUS_NO_READY; }Copy the code

Specific code in GitHub above, there is a need for a friend can refer to.