AudioTrack是什么?
官方释义:管理和播放单个音频资源。将PCM音频缓冲区流传输到音频接收器以进行播放。
AudioTrack和其他播放器的区别
播放器 | 区别/关系 |
---|---|
AudioTrack | 只能播放PCM(.wav格式的音频文件)数据流 |
MediaPlayer | 本质是先将.mp3等格式解码成PCM流,然后底层会调用AudioTrack进行播放 |
SoundPool | 短音频,播放提示音,按键音之类的音频 |
AudioTrack的使用流程
1.初始化AudioTrack。
2.将数据从流或者文件中不断的写入缓存。(静态模式会一次性全部写入缓存)
2.不断的将音频流从缓存中拿出来(静态模式会一次性全部拿出来)
3.将拿到的缓存数据,不断的播放出来。
4.停止或者销毁。
使用AudioTrack前的基本概念
1.AudioTrack的两种传输模式(TransferMode):
传输模式(TransferMode) | 区别 | 操作 |
---|---|---|
AudioTrack.MODE_STATIC | 长(流)音频,多次读取,可能存在延迟,需要将数据一次一次拷贝到AudioTrack的缓存中。 | 先play,然后不停的write。 |
AudioTrack.MODE_STREAM | 短音频,一次读完。如果数据过长,可能会直接奔掉,只需将数据一次性全部拷贝到AudioTrack的缓存中。 | 必须先write 将数据一次加载完成,再play |
2.AudioTrack的基本构造
audioTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(8000)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build())
.setTransferMode(AudioTrack.MODE_STREAM)
.setSessionId(0)
.build();
构造中各种参数的含义
1.setAudioAttributes()
主要用来设置音频属性
- setUsage()
音频的用途:
比如是要作为音乐播放,
还是手机导航语音播放,
还是作为收到消息的提示音使用,等。
如:(还有很多,这里只列举3种)
AudioAttributes.USAGE_MEDIA
AudioAttributes.USAGE_ALARM
AudioAttributes.USAGE_VOICE_COMMUNICATION
- setContentType()
音频的内容:
比如当前要播放的音频内容是聊天语音,
还是一段音乐,还是按键音效,等。
如:(还有很多,这里只列举3种)
AudioAttributes.CONTENT_TYPE_SPEECH
AudioAttributes.CONTENT_TYPE_MUSIC
AudioAttributes.CONTENT_TYPE_MOVIE
设置这些属性有到底有什么用?
这取决于Android系统的运行策略。
不知道大伙有没有这种情况:
正在听音乐,听的很high。突然电话来了,你会发现你的音乐声音会自动变小或者关闭。当你结束电话,音频又会开始播放。
这是就是setAudioAttributes()所起到的作用。帮你判断目前音频的使用环境,并做出相应处理。
2.setAudioFormat()
- setEncoding(音频采样大小)
指一秒内传输的数据大小。采样大小越高声音越饱满。
目前只有两种模式:
ENCODING_PCM_8BIT
ENCODING_PCM_16BIT
- setSampleRate(采样率)
指录音设备在一秒钟内对声音信号的采样次数,采样率越高声音越细腻。
我目前项目里是16000Hz。
官方说44100Hz可以兼容所有值。(目前大部分app用的应该也是44100Hz)
- setChannelMask(通道数)
通道,通俗点就是耳机的声道,听歌的双声道,打游戏的5.1声道,7.1声道,就是这个声道。
AudioFormat.CHANNEL_OUT_MONO
AudioFormat.CHANNEL_OUT_STEREO
AudioFormat.CHANNEL_OUT_7POINT1_SURROUND
3.setTransferMode()
传输模式,上面讲到的 静态 和 流式。
AudioTrack.MODE_STREAM
AudioTrack.MODE_STATIC
4.setBufferSizeInBytes(最小缓存区大小)
还有个概念,就是minBufferSize,它是干嘛的?
应用层能分配多大的数据Buffer。也就是保障AudioTrack正常工作的最小缓冲区大小。
也就是播放当前音频每一帧需要的缓存大小。
它的计算方式
AudioTrack.getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
举例:
int minBufferSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
也就是上面构造中setAudioFormat()方法里的三个参数。
构造中有.setBufferSizeInBytes()这个方法,就是用来设置这个值的。
3.AudioTrack的其他构造
- new AudioTrack.Builder().setAudioAttributes().setAudioFormat().setBufferSizeInBytes().setTransferMode().build(); [最推荐的用法]
- new AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId); [和Builder类似,但是需要自行调用getMinBufferSize计算一下缓存大小]
- new AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) [早期API3-9的方法,官方已不推荐使用]
4.开始播放音频
不管哪种构造方法,播放还是一样的。
播放前的判空
audioTrack == null //是否为空
audioTrack.getState() != AudioTrack.STATE_INITIALIZED //是否已经初始化
audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) //是否正在播放(这一条根据自己业务需求而定)
初始化状态 和 播放状态
1.初始化状态
AudioTrack.STATE_UNINITIALIZED 未初始化(这个时候直接播放,会奔溃)
AudioTrack.STATE_INITIALIZED 已初始化
AudioTrack.STATE_NO_STATIC_DATA 静态播放流已加载
2.播放状态
AudioTrack.PLAYSTATE_STOPPED 播放停止
AudioTrack.PLAYSTATE_PAUSED 播放暂停
AudioTrack.PLAYSTATE_PLAYING 播放中
静态播放
先说静态播放,前面说了,一次性全部读取。
public void staticPlay() {
//这里以raw资源文件为例:因为是短资源,所以直接放在资源文件中了。
byte[] staticData = new byte[0];
InputStream in = getResources().openRawResource(R.raw.msg);
try {
int sizeOfInputStram = in.available();
staticData = new byte[sizeOfInputStram];
Log.i("ddd", "文件大小:" + sizeOfInputStram);
ByteArrayOutputStream out = new ByteArrayOutputStream(sizeOfInputStram);
// for (int b; (b = in.read()) != -1; ) {
// out.write(b);
// }
int rc;
while ((rc = in.read(staticData, 0, sizeOfInputStram)) != -1) {
out.write(staticData, 0, rc);
}
staticData = out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
staticAudioTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.setLegacyStreamType(AudioManager.STREAM_MUSIC)
.build())
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(staticData.length)//由于短音频一般会在一秒内播完,所以推荐使用资源的大小作为参数
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build())
.setBufferSizeInBytes(staticData.length)//这里设置资源的长度,因为是静态的短音频
.setTransferMode(AudioTrack.MODE_STATIC)//这里是静态模式
.setSessionId(0)
.build();
Log.i("ddd", "静态大小:" + staticData.length);
//一次性写入AudioTrack
staticAudioTrack.write(staticData, 0, staticData.length);
//播放
staticAudioTrack.play();
}
流式播放
先开始播放,再不断的读取
//这里以读取file文件为例,因为文件较大,放在资源文件中不太合理。
//流式初始化
audioTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(44100)//根据实际项目而定
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build())
.setTransferMode(AudioTrack.MODE_STREAM)//流式模式
.setSessionId(0)
.build();
//计算一次读取的大小
int minBufferSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
//这里以读取一个长文件为例
file = new File("文件路径");
fileInputStream = new FileInputStream(file);
dataInputStream = new DataInputStream(new BufferedInputStream(fileInputStream))
byte[] bytes = new byte[minBufferSize];//(这里的bytes就是上面通过getMinBufferSize计算出来的长度值)
int len;
audioTrack.play();
while ((len = dataInputStream.read(bytes)) != -1) {
//每次读取minBufferSize的长度
audioTrack.write(bytes, 0, len);
}
audioTrack.stop();//这里在播放完成时调用stop
audioTrack.release()
释放资源。
当不需要或者使用完audioTrack时,可调用此方法释放掉资源和占用你的内存。
因为这个方法调用后,audioTrack状态将回到 未初始化 状态,需要重新调用构造函数。
所以,还需要使用时,无需调用此方法。只需stop停止即可。当确定不再使用(比如退出当前页面,这个音频只是播放一次)时,可调用此方法释放掉。
调用此方法后最好再置空。
audioTrack = null//将audioTrak置空