Skip to content

Instantly share code, notes, and snippets.

@AssIstne
Created April 12, 2018 01:39
Show Gist options
  • Save AssIstne/d30dba0a0e52d379898186dc67a521e9 to your computer and use it in GitHub Desktop.
Save AssIstne/d30dba0a0e52d379898186dc67a521e9 to your computer and use it in GitHub Desktop.
play pcm file
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() {
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment