一、前言

说到在 Android 平台上播放音频,我们最先想到的是 MediaPlayer。系统 API 对其做了比较全面的封装,开发者用少量的代码就能实现播放功能。MediaPlayer 可以播放多种格式的声音文件,例如 MP3,AAC,WAV,OGG,MIDI 等,而 AudioTrack 只能播放 PCM 数据流。
       实际上,MediaPlayer 在播放音频时,在 Framework 层还是会创建 AudioTrack,把解码后的 PCM 数流传递给 AudioTrack,最后由 AudioFlinger 进行混音,把音频传递给硬件播放出来。利用 AudioTrack 播放只是跳过 MediaPlayer 的解码部分而已。如果是实时的音频数据,那么只能用 AudioTrack 进行播放。
       AudioTrack 有两种数据加载模式(MODE_STREAM 和 MODE_STATIC), 对应着两种完全不同的使用场景。
       1.MODE_STREAM:在这种模式下,通过 write 一次次把音频数据写到 AudioTrack 中。这和平时通过 write 调用往文件中写数据类似,但这种方式每次都需要把数据从用户提供的 Buffer 中拷贝到 AudioTrack 内部的 Buffer 中,在一定程度上会使引起延时。为解决这一问题,AudioTrack 就引入了第二种模式。
       2.MODE_STATIC:在这种模式下,只需要在 play 之前通过一次 write 调用,把所有数据传递到 AudioTrack 中的内部缓冲区,后续就不必再传递数据了。这种模式适用于像铃声这种内存占用较小、延时要求较高的文件。但它也有一个缺点,就是一次 write 的数据不能太多,否则系统无法分配足够的内存来存储全部数据。
       在 AudioTrack 构造函数中,会接触到 AudioManager.STREAM_MUSIC 这个参数。它的含义与 Android 系统对音频流的管理和分类有关。Android 将系统的声音分为好几种流类型,下面是几个常见的:
       STREAM_ALARM:警告声
       STREAM_MUSIC:音乐声,例如 music 等
       STREAM_RING:铃声
       STREAM_SYSTEM:系统声音,例如低电提示音,锁屏音等
       STREAM_VOICE_CALL:通话声
       注意:上面这些类型的划分和音频数据本身并没有关系。例如 MUSIC 和 RING 类型都可以是某首 MP3 歌曲。另外,声音流类型的选择没有固定的标准,例如,铃声预览中的铃声可以设置为 MUSIC 类型。音频流类型的划分和 Audio 系统对音频的管理策略有关。

二、我们用代码实践一下播放的流程

1  构造 AudioTrack 实例

public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes, int mode) throws IllegalArgumentException {this(streamType, sampleRateInHz, channelConfig, audioFormat,bufferSizeInBytes, mode, AudioManager.AUDIO_SESSION_ID_GENERATE);
}public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes, int mode) throws IllegalArgumentException {this(streamType, sampleRateInHz, channelConfig, audioFormat,bufferSizeInBytes, mode, AudioManager.AUDIO_SESSION_ID_GENERATE);
}public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes, int mode, int sessionId) throws IllegalArgumentException {// mState already == STATE_UNINITIALIZEDthis((new AudioAttributes.Builder()).setLegacyStreamType(streamType).build(),(new AudioFormat.Builder()).setChannelMask(channelConfig).setEncoding(audioFormat).setSampleRate(sampleRateInHz).build(),bufferSizeInBytes,mode, sessionId);deprecateStreamTypeForPlayback(streamType, "AudioTrack", "AudioTrack()");
}

在采样 pcm 音频数据需要设置对应的采样率,采样精度,采样的通道数和采样的缓冲区大小,如果播放的音频是使用 AudioRecord 录制的,那么这些参数配置信息需要和 AudioRerord一致,不然播放就会出现奇怪的问题。

1.1 AudioTrack 播放音频时会有两种方式

音频播放的方式,有两种方式 MODE_STATIC 或者 MODE_STREAM 。

  • MODE_STATIC:预先将需要播放的音频数据读取到内存中,然后才开始播放。
  • MODE_STREAM:边读边播,不会将数据直接加载到内存

1.2 MODE_STREAM 的方式构建 AudioTrack 实例

/*** 构建 AudioTrack 实例对象*/
private void createStreamModeAudioTrack() {if (audioTrack == null) {bufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,     AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);}
}

1.3 MODE_STATIC 的方式构建 AudioTrack 实例

/*** 构建 AudioTrack 实例对象*/
private void createStreamModeAudioTrack() {if (audioTrack == null) {//file 就是需要播放的音频文件,这里的buffersize就是文件的大小audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,44100, AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT, (int) file.length(), AudioTrack.MODE_STATIC);}
}

2 写入数据

不间断通过write方法的写数据给AudioTrack。

对于 MODE_STREAM 写入数据,会阻塞,直到写入的数据都传输给 AudioTrack。
   对于 MODE_STATIC 会将数据拷贝到缓冲区中,并在该方法返回后执行 play() 方法播放音频数据。

  • int write (byte[] audioData, int offsetInBytes,int sizeInBytes)
  • int write (short[] audioData, int offsetInShorts, int sizeInShorts)
  • int write (float[] audioData, int offsetInFloats, int sizeInFloats,int writeMode)

该方法的返回值:
正确:>=0 该值表示写入的数据量。
错误:<0

  • ERROR_INVALID_OPERATION
  • ERROR_BAD_VALUE
  • ERROR_DEAD_OBJECT
  • ERROR

2.1 两种方式写入数据的区别

  • MODE_STATIC

在 AudioTrack 创建之处,会初始化一个与其相关联的 buffer 缓冲区,这个缓冲区的大小是在构造方法指定的。这个大小表示 AudioTrack 可以播放多久。对于 MODE_STATIC 这种模式下,这个 buffer 的大小就是需要播放的文件或者流的大小。

//写入数据大小 array 就是预先将音频数据加载到array数组中
int writeResult = audioTrack.write(array, 0, array.length);
//检查写入的结果,如果是异常情况,则直接需要释放资源
if (writeResult == AudioTrack.ERROR_INVALID_OPERATION || writeResult == AudioTrack.ERROR_BAD_VALUE|| writeResult == AudioTrack.ERROR_DEAD_OBJECT || writeResult == AudioTrack.ERROR) {//出异常情况isPlaying = false;release();return;
}
  • MODE_STREAM

使用这种方式是通过将数据写入到缓冲区中,而需要注意写入到这个缓冲区的数据大小,需要确保小于或者等于这个构造 AudioTrack 的缓冲区大小。
       AudioTrack 不是 final 类型,也就是说可以使用继承实现自己的功能,但是官方文档表示不建议这样做。

 //边读边播byte[] buffer = new byte[bufferSize];while (fis.available() > 0) {int readCount = fis.read(buffer);if (readCount == -1) {Log.e(TAG, "没有更多数据可以读取了");break;}int writeResult = audioTrack.write(buffer, 0, readCount);if (writeResult >= 0) {//success} else {//fail//丢掉这一块数据continue;}}

这个缓冲区大小可以通过 AudioTrack.getMinBufferSize 来获取

bufferSize = AudioTrack.getMinBufferSize(44000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);

3 状态判断

3.1 AudioTrack 状态判断

检测一个已经创建好的 AudioTrack 的状态,确保操作在正确初始化之后进行。当需要进行播放前,校验 AudioTrack 是否处于正确的状态。

/*** State of an AudioTrack that was not successfully initialized upon creation.*/
public static final int STATE_UNINITIALIZED = 0;
/*** State of an AudioTrack that is ready to be used.*/
public static final int STATE_INITIALIZED   = 1;
/*** State of a successfully initialized AudioTrack that uses static data,* but that hasn't received that data yet.*/
public static final int STATE_NO_STATIC_DATA = 2;/*** Returns the state of the AudioTrack instance. This is useful after the* AudioTrack instance has been created to check if it was initialized* properly. This ensures that the appropriate resources have been acquired.* @see #STATE_UNINITIALIZED   表示 AudioTrack 创建时没有成功地初始化* @see #STATE_INITIALIZED     表示 AudioTrack 已经是可以使用了* @see #STATE_NO_STATIC_DATA  表示当前是使用 MODE_STATIC ,但是还没往缓冲区中写入数据。*                             当接收数据之后会变为 STATE_INITIALIZED 状态                */
public int getState() {return mState;
}
//播放时,状态校验
if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {Log.e(TAG, "不能播放,当前播放器未处于初始化状态..");return;
}

3.2 AudioTrack 播放状态

/** indicates AudioTrack state is stopped */
public static final int PLAYSTATE_STOPPED = 1;  // matches SL_PLAYSTATE_STOPPED
/** indicates AudioTrack state is paused */
public static final int PLAYSTATE_PAUSED  = 2;  // matches SL_PLAYSTATE_PAUSED
/** indicates AudioTrack state is playing */
public static final int PLAYSTATE_PLAYING = 3;  // matches SL_PLAYSTATE_PLAYING/*** Returns the playback state of the AudioTrack instance.* @see #PLAYSTATE_STOPPED  停止* @see #PLAYSTATE_PAUSED   暂停* @see #PLAYSTATE_PLAYING  正在播放*/
public int getPlayState() {synchronized (mPlayStateLock) {return mPlayState;}
}

4 播放 play

  • 对于 MODE_STATIC 模式,必须要调用 write(...) 相关方法将数据写入到对应的缓冲区中,然后才可以调用 paly(...) 方法进行播放操作。
//先将所有的数据写入到缓冲区
write(...)
//然后在播放
play(...)
  • 对于 MODE_STREAM 模式,必须要调用 play(...) 方法,然后调用 write(...) 方法进行写入数据播放操作。
paly(...)new Thread() {public void run() {//一系列的 write 操作write(...)}
}.start();
/*** 播放,使用stream模式*/
private void playInModeStream() {final int minBufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT);audioTrack = new AudioTrack(new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build(),new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ).setEncoding(AUDIO_FORMAT).setChannelMask(channelConfig).build(),minBufferSize,AudioTrack.MODE_STREAM,AudioManager.AUDIO_SESSION_ID_GENERATE);audioTrack.play();File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");try {fileInputStream = new FileInputStream(file);new Thread(new Runnable() {@Overridepublic void run() {try {byte[] tempBuffer = new byte[minBufferSize];while (fileInputStream.available() > 0) {int readCount = fileInputStream.read(tempBuffer);if (readCount == AudioTrack.ERROR_INVALID_OPERATION ||readCount == AudioTrack.ERROR_BAD_VALUE) {continue;}if (readCount != 0 && readCount != -1) {audioTrack.write(tempBuffer, 0, readCount);}}} catch (IOException e) {e.printStackTrace();}}}).start();} catch (IOException e) {e.printStackTrace();}
}

5 AudioTrack 状态

5.1 停止播放

/*** Stops playing the audio data.* When used on an instance created in {@link #MODE_STREAM} mode, audio will stop playing* after the last buffer that was written has been played. For an immediate stop, use* {@link #pause()}, followed by {@link #flush()} to discard audio data that hasn't been played* back yet.* 对于 MODE_STREAM 模式,如果单是调用 stop 方法, AudioTrack 会等待缓冲的最后一帧数据播放完毕之* 后,才会停止,如果需要立即停止,那么就需要调用 pause 然后调用 flush 这两个方法,那么 * AudioTrack 就是丢缓冲区中剩余的数据。* @throws IllegalStateException*/
public void stop() throws IllegalStateException {...
}

5.2 暂停

/*** Pauses the playback of the audio data. Data that has not been played* back will not be discarded. Subsequent calls to {@link #play} will play* this data back. See {@link #flush()} to discard this data.* 暂停播放,但是缓冲区中没有被播放的数据不会被舍弃,调用 play 方法即可接着播放。* @throws IllegalStateException*/
public void pause() throws IllegalStateException {...
}

5.3 刷新

/*** 刷新正在排队播放的音频数据,调用该方法会将写入到缓冲区但没有被播放的音频数据都会被丢弃。* 如果是非 STREAM 或者没有执行 pasuse 或者 stop 将不会有任何效果。*/
public void flush() {...
}

5.4 释放

/*** Releases the native AudioTrack resources.* 释放本地 AudioTrack 资源*/
public void release() {...
}

5.5 示例代码

public void stop() { if ((audioTrack != null) && (audioTrack.getState() == AudioTrack.STATE_INITIALIZED)) {if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_STOPPED) {audioTrack.flush();audioTrack.stop();}}
} 

三、 AudioTrack 和 MediaPlayer 的对比

播放声音可以用MediaPlayer和AudioTrack,两者都提供了Java API供应用开发者使用。虽然都可以播放声音,但两者还是有很大的区别的。

6.1 区别

其中最大的区别是MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等。MediaPlayer会在framework层创建对应的音频解码器。而AudioTrack只能播放已经解码的PCM流,如果对比支持的文件格式的话则是AudioTrack只支持wav格式的音频文件,因为wav格式的音频文件大部分都是PCM流。AudioTrack不创建解码器,所以只能播放不需要解码的wav文件。

6.2 联系

MediaPlayer在framework层还是会创建AudioTrack,把解码后的PCM数流传递给AudioTrack,AudioTrack再传递给AudioFlinger进行混音,然后才传递给硬件播放,所以是MediaPlayer包含了AudioTrack。

每一个音频流对应着一个AudioTrack类的一个实例,每个AudioTrack会在创建时注册到 AudioFlinger中,由AudioFlinger把所有的AudioTrack进行混合(Mixer),然后输送到AudioHardware中进行播放,目前Android同时最多可以创建32个音频流,也就是说,Mixer最多会同时处理32个AudioTrack的数据流。

6.3 SoundPool

在接触Android音频播放API的时候,发现SoundPool也可以用于播放音频。下面是三者的使用场景:MediaPlayer 更加适合在后台长时间播放本地音乐文件或者在线的流式资源; SoundPool 则适合播放比较短的音频片段,比如游戏声音、按键声、铃声片段等等,它可以同时播放多个音频; 而 AudioTrack 则更接近底层,提供了非常强大的控制能力,支持低延迟播放,适合流媒体和VoIP语音电话等场景。

参考文章:

  • Android音频开发之AudioTrack
  • Android 音视频开发(三):使用 AudioTrack 播放PCM音频

AudioTrack播放PCM音频相关推荐

  1. AudioTrack 播放PCM音频数据

    AudioTrack 可以用来播放PCM数据,上一篇博客我讲了AudioRecord可以录制PCM数据 AudioTrack实例可以在两种模式下运行:静态或流式传输. 在Streaming模式下,应用 ...

  2. 音视频开发系列(28)AudioTrack播放PCM音频

    目录 AudioTrack和MediaPlayer AudioTrack的API介绍(构造.操作.状态机) 具体实现(Static和Stream两种模式) 遇到的问题 收获 一.MediaPlayer ...

  3. AudioTrack播放pcm格式音频

    AudioTrack播放pcm格式音频 package com.zero.demo;import android.content.Context; import android.media.Audio ...

  4. NDK学习笔记:JNI调用Java层方法创建Native的AudioTrack播放PCM(方法签名,CallXXXMethod)

    NDK学习笔记:JNI调用Java层方法创建Native的AudioTrack播放PCM 题目有点复杂,不过确实就是那么回事.这章想记录的内容比较多,先列出来: native static 与 nat ...

  5. Android使用AudioTrack播放WAV音频文件

    目录 1.wav文件格式 2.wav文件解析 3.wav文件播放 QA: 开始播放wav的时候使用了系统的播放器mediaplayer进行播放,但是无奈mediaplayer支持的实在不好. 好些年前 ...

  6. SDL播放PCM音频数据

    SDL播放PCM音频数据 1.PCM简介    PCM(Pulse CodeModulation,脉冲编码调制)音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样.量化.编码转换成的标准数字 ...

  7. windows下使用Qt播放PCM音频文件(通过QAudioOutput和QIODevice)

    在博主之前的博文<windows下使用FFmpeg生成PCM音频文件并播放(通过命令的方式)>(链接https://blog.csdn.net/u014552102/article/det ...

  8. Android直播开发之旅(13):使用FFmpeg+OpenSL ES播放PCM音频

    文章目录 1. OpenSL ES原理 1.1 OpenSL ES核心API讲解 1.1.1 对象(Object)与接口(Interface) 1.1.2 [OpenSL ES的状态机制](https ...

  9. FFmpeg从入门到入魔(4):OpenSL ES播放PCM音频

    1. OpenSL ES原理  OpenSL ES(Open Sound Library for Embedded Systems),即嵌入式音频加速标准,是一个无授权费.跨平台.针对嵌入式系统精心优 ...

最新文章

  1. 【深度学习入门到精通系列】对比度受限的自适应直方图均衡化(CLAHE)
  2. StringBuffer的用法
  3. 调用一次fork返回2次
  4. Ubuntu 16.04上搭建CDH5.16.1集群
  5. `>>`(有符号右移) 和 `>>>`(无符号右移)的区别
  6. linux安装openssl
  7. python与sap_Python结合SAP GUI Script操作sap的简易教程
  8. 斐讯K2破解任意校园网教程
  9. B端产品运营基本工作内容
  10. VMware 安装ghost win7 gho
  11. 云帆加速扶凯:坚守本源 做技术流的CDN
  12. 基于Html5 的canvas容器实现定制印章(圆形、椭圆、方形)
  13. Vue 使用 yarn 报错
  14. Unity API常用方法和类学习笔记1
  15. table表格单元格padding_html表格单元格间距
  16. 测量用计算机软件管理办法,《计算机应用基础》测试题(一)
  17. i711800h和r54600h哪个好
  18. Scrapy采集“人民的名义”豆瓣评价实验报告
  19. 如何用浏览器调试网页前端代码?
  20. kkFileView word、 excel、 ppt、 pdf在线预览

热门文章

  1. 搞笑囧事还真多!!!!
  2. 无线网络hack学习1
  3. Unity 优化(Draw Call)
  4. 数据可视化----ECharts---折线图(四)
  5. 南邮电工电子知识点梳理
  6. 01 |「沟通技巧」
  7. 对OSI 7层模型的理解
  8. thinkpad选择启动项_thinkpad启动项设置
  9. 【生态大会】数澜科技助力企业IT伙伴继续掘金DT时代
  10. [Mysql] LAG()函数 | LEAD()函数