Created
April 12, 2018 01:39
-
-
Save AssIstne/d30dba0a0e52d379898186dc67a521e9 to your computer and use it in GitHub Desktop.
play pcm file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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