hienlt0610
10/1/2019 - 5:05 AM

PCMAudioPlayer.java

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.AudioTrack.OnPlaybackPositionUpdateListener;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.UiThread;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * Des:
 * 播放pcm文件
 *
 * @author assistne
 * @since 2018/2/24
 */
class PCMAudioPlayer {

    private static final int RATE_IN_HZ = 16000;

    // AudioTrack 播放缓冲大小
    private int mMinBufferSize;

    private Handler mUIHandler;
    private final Executor mPlayWorker = Executors.newSingleThreadExecutor();

    private volatile AudioTrack mAudioTrack;
    private volatile OnPlayListener mListener;
    private volatile boolean mHasStopped;
    private boolean mHasReturnCompletion;

    PCMAudioPlayer() {
        mUIHandler = new Handler(Looper.getMainLooper());
        initAudioTrack();
    }

    private void initAudioTrack() {
        if (mAudioTrack != null) {
            // 创建新实例前, 把旧实例释放掉
            stop();
            release();
        }
        // AudioTrack 得到播放最小缓冲区的大小
        mMinBufferSize = AudioTrack.getMinBufferSize(RATE_IN_HZ,
            AudioFormat.CHANNEL_CONFIGURATION_MONO,
            AudioFormat.ENCODING_PCM_16BIT);
        // 实例化播放音频对象
        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, RATE_IN_HZ,
            AudioFormat.CHANNEL_CONFIGURATION_MONO,
            AudioFormat.ENCODING_PCM_16BIT, mMinBufferSize,
            AudioTrack.MODE_STREAM);
        mAudioTrack.setPlaybackPositionUpdateListener(new OnPlaybackPositionUpdateListener() {
            @Override
            public void onMarkerReached(AudioTrack track) {
                // 播放完毕回调, 有毫米级误差, 所以延迟1s再回调接口, 保证已经播放完毕
                dispatchCompletion(500);
            }

            @Override
            public void onPeriodicNotification(AudioTrack track) {
                /* no-op */
            }
        });
    }

    void setOnPlayListener(OnPlayListener listener) {
        mListener = listener;
    }

    private void playInBg(File file) {
        if (file == null) {
            return;
        }
        if (mAudioTrack == null) {
            initAudioTrack();
        }

        byte[] byteData = new byte[mMinBufferSize];
        int markerRes = mAudioTrack.setNotificationMarkerPosition(
            mAudioTrack.getPlaybackHeadPosition() + getFinalFrame(file) - 200);
        if (markerRes != AudioTrack.SUCCESS) {
            // 设置回调失败
            dispatchError();
            return;
        }

        FileInputStream in;
        try {
            in = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            dispatchError();
            return;
        }

        try {
            mAudioTrack.play();
            dispatchStart();
        } catch (IllegalStateException e) {
            // 状态异常
            dispatchError();
            release();
            return;
        }

        mHasReturnCompletion = false;
        // 音频较短时setNotificationMarkerPosition可能失效, 所以设个定时器回调以防万一
        dispatchCompletion(getDuration(file) + 500);

        mHasStopped = false;
        int totalReadSize = 0;
        int size = (int) file.length();
        try {
            int readSize = 0;
            while (totalReadSize < size && readSize != -1) {
                if (mHasStopped) {
                    // 被停止, 快速退出
                    return;
                }
                // 耗时操作
                readSize = in.read(byteData, 0, byteData.length);
                totalReadSize += readSize;
                // 耗时操作前后都做检查
                if (mHasStopped) {
                    return;
                }
                // 非空检查, 以防万一
                if (mAudioTrack != null) {
                    mAudioTrack.write(byteData, 0, readSize);
                } else {
                    // 快速退出
                    return;
                }
            }
        } catch (IOException e) {
            dispatchError();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                /* no-op */
            }
        }

        if (mAudioTrack != null) {
            try {
                mAudioTrack.stop();
            } catch (IllegalStateException e) {
                dispatchError();
                // 状态异常
                release();
            }
        }
    }

    /**
     * 播放文件
     *
     * @param file pcm文件
     */
    @AnyThread
    void play(final File file) {
        mPlayWorker.execute(() -> playInBg(file));
    }

    /**
     * 立即停止播放
     */
    void stop() {
        mUIHandler.removeCallbacks(mReturnCompletionRunnable);
        mHasReturnCompletion = false;
        mHasStopped = true;
        if (mAudioTrack != null) {
            try {
                // 马上停止播放需要暂停, 清数据
                mAudioTrack.pause();
                mAudioTrack.flush();
            } catch (IllegalStateException e) {
                /* no-op */
            }
        }
    }

    /**
     * 释放资源
     */
    void release() {
        // 先stop, 再release
        stop();
        if (mAudioTrack != null) {
            mAudioTrack.release();
            mAudioTrack = null;
        }
    }

    private void dispatchError() {
        if (mListener != null) {
            mUIHandler.post(() -> {
                if (mListener != null) {
                    mListener.onError();
                }
            });
        }
    }

    private void dispatchStart() {
        if (mListener != null) {
            mUIHandler.post(() -> {
                if (mListener != null) {
                    mListener.onBeforePlay();
                }
            });
        }
    }

    private void dispatchCompletion(long delay) {
        if (mListener != null) {
            mUIHandler.removeCallbacks(mReturnCompletionRunnable);
            mUIHandler.postDelayed(mReturnCompletionRunnable, delay);
        }
    }

    private Runnable mReturnCompletionRunnable = () -> {
        if (mHasReturnCompletion) {
            return;
        }
        mHasReturnCompletion = true;
        if (mListener != null) {
            mListener.onCompletion();
        }
    };

    /**
     * @param file pcm文件
     * @return 根据文件大小返回pcm文件的播放时长, 单位毫秒
     */
    static long getDuration(@NonNull File file) {
        final long fileSize = file.length();
        // 文件大小 = 采样位数(16bit) * 采样率(16KHZ) * 声道数(mono单声道, 1) * 秒数 / 8
        return (long) ((fileSize * 8D) / (16D * RATE_IN_HZ) * 1000L);
    }

    private int getFinalFrame(@NonNull File file) {
        return (int) getDuration(file) / 1000 * RATE_IN_HZ;
    }

    /**
     * 播放回调接口
     *
     * @author assistne
     * @since 2018/2/24
     */
    static class OnPlayListener {

        /**
         * 任意环节报错时回调
         */
        @UiThread
        void onError() {

        }

        /**
         * 开始播放前时回调
         */
        @UiThread
        void onBeforePlay() {

        }

        /**
         * 播放完成时回调, 有毫秒级误差
         */
        @UiThread
        void onCompletion() {

        }
    }
}