脉冲编码调制(Pulse Code Modulation,PCM),它的作用是把一个时间连续,取值连续的模拟信号变换成时间离散,取值离散的数字信号后在信道中传输。脉冲编码调制就是对模拟信号先抽样,再对样值幅度量化,编码的过程。
pcm是一个通信上的概念,脉冲编码调制,是编码。wav是媒体概念,体现的是封装。wav文件可以封装pcm编码信息,也可以封装其他编码格式,例如mp3等
从手机麦克风采集的数据就是pcm原始数据。
首先它可以直接播放pcm音频数据,但是不能播放其它的格式如MP3,AAC,WAV等,不过更加上层的API如MediaPlay却可以播放这些格式,但是同时MediaPlay也失去了对底层数据流的一些操作,如AudioTrack可以控制每一帧数据,可以自己转化任意格式,自由性比较强大,而MediaPlay却不行。
AudioTrack播放有两种模式:
在AudioTrack构造函数中,会接触到AudioManager.STREAM_MUSIC这个参数。
Android将系统的声音streamType分为好几种流类型,下面是几个常见的:
注意:上面这些类型的划分和音频数据本身并没有关系。例如MUSIC和RING类型都可以是某首MP3歌曲。另外,声音流类型的选择没有固定的标准,例如,铃声预览中的铃声可以设置为MUSIC类型。音频流类型的划分和Audio系统对音频的管理策略有关。
构造有多种:
下面两种对于低版本api可以用:
AudioManager.AUDIO_SESSION_ID_GENERATE
if the session isn't known at construction time。就是说在创建的时候不知道会话可以传入这个值AudioManager.AUDIO_SESSION_ID_GENERATE
。AudioTrack.Builder
or AudioTrack(AudioAttributes, AudioFormat, int, int, int)
to specify the AudioAttributes
instead of the stream type which is only for volume control.另外一种:
后面三个int参数就不用介绍了,上面有,介绍前两个参数(not null)。
format实例描述将通过此AudioTrack播放的数据的格式。见AudioFormat.Builder
用于配置音频格式参数,如编码数据格式、通道和采样率,就是将之前的api的三个参数(数据格式,通道,采样率)合为一体,很容易理解。
attributes则是对之前的api的streamType的 一个改进。
streamType是描述音频的播放行为,是用来表述声音是用于干嘛的,在AudioAttributes可以用setUsage()设置,有以下值:
int |
USAGE_ALARM 当使用是警报时使用的用法值(例如: |
int |
USAGE_ASSISTANCE_ACCESSIBILITY 当使用是用于可访问性时使用的用法值,例如与屏幕读取器一起使用。 |
int |
USAGE_ASSISTANCE_NAVIGATION_GUIDANCE 当使用是驱动或导航指示时使用的用法值。 |
int |
USAGE_ASSISTANCE_SONIFICATION 当使用是声纳化时使用的用法值,例如用户界面声音。 |
int |
USAGE_ASSISTANT 用于用户查询、音频指令或帮助语句的音频响应的用法值。 |
int |
USAGE_GAME 当使用是用于游戏音频时使用的用法值。 |
int |
USAGE_MEDIA 当使用是媒体(如音乐或电影配乐)时使用的用法值。 |
int |
USAGE_NOTIFICATION 当使用是通知时要使用的用法值。 |
int |
USAGE_NOTIFICATION_COMMUNICATION_DELAYED 当使用是非即时通信类型(如电子邮件)的通知时,要使用的使用值。 |
int |
USAGE_NOTIFICATION_COMMUNICATION_INSTANT 当使用是“即时”通信(如聊天或SMS)的通知时使用的用法值。 |
int |
USAGE_NOTIFICATION_COMMUNICATION_REQUEST 当使用是输入/结束通信的请求时使用的用法值,例如VoIP通信或视频会议。 |
int |
USAGE_NOTIFICATION_EVENT 当使用是为了吸引用户的注意时使用的使用价值,例如提醒或低电量警告。 |
int |
USAGE_NOTIFICATION_RINGTONE 当使用是电话铃声时使用的使用价值。 |
int |
USAGE_UNKNOWN 用法未知时使用的用法值。 |
int |
USAGE_VOICE_COMMUNICATION 当使用是语音通信时使用的使用价值,如电话或VoIP。 |
int |
USAGE_VOICE_COMMUNICATION_SIGNALLING 当使用是在呼叫信号时使用的用法值,例如使用“繁忙”的嘟嘟声或DTMF音调。 |
而AudioAttributes多加了一个东西就是内容类型,通过setContentType()设置,有以下值:
int |
CONTENT_TYPE_MOVIE 当内容类型是配乐时使用的内容类型值,通常伴随电影或电视节目。 |
int |
CONTENT_TYPE_MUSIC 当内容类型是音乐时要使用的内容类型值。 |
int |
CONTENT_TYPE_SONIFICATION 当内容类型是用于伴随用户操作的声音时使用的内容类型值,例如表示键单击的响声或声音效果,或事件,例如在游戏中接收奖励的声音的类型。 |
int |
CONTENT_TYPE_SPEECH 内容类型为语音时使用的内容类型值。 |
int |
CONTENT_TYPE_UNKNOWN 当内容类型未知时使用的内容类型值,或定义的内容类型值以外的值。 |
//音频采样率 (MediaRecoder的采样率通常是8000Hz AAC的通常是44100Hz.设置采样率为44100目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)
private static final int mSampleRateInHz = 44100; //声道
private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO; //单声道
//数据格式 (指定采样的数据的格式和每次采样的大小) //指定音频量化位数 ,在AudioFormaat类中指定了以下各种可能的常量。通常我们选择ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脉冲编码调制,它实际上是原始音频样本。 //因此可以设置每个样本的分辨率为16位或者8位,16位将占用更多的空间和处理能力,表示的音频也更加接近真实。
private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
private static final String mFileName = Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"audiorecordtest.pcm";
private void playPCM_STREAM() throws FileNotFoundException {
if (maudioTrack != null){
maudioTrack.stop();
maudioTrack.release();
maudioTrack = null;
}
//先估算最小缓冲区大小
mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz,mChannelConfig,mAudioFormat);
//创建AudioTrack
maudioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder()
.setSampleRate(mSampleRateInHz)
.setEncoding(mAudioFormat)
.setChannelMask(mChannelConfig)
.build(),
mBufferSizeInBytes,
AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE
);
maudioTrack.play(); 这个模式需要先play
File file = new File(mFileName); //原始pcm文件
final FileInputStream fileInputStream;
if (file.exists()){
fileInputStream = new FileInputStream(file);
new Thread(){
@Override
public void run() {
try {
byte[] buffer = new byte[mBufferSizeInBytes];
while(fileInputStream.available() > 0){
int readCount = fileInputStream.read(buffer); //一次次的读取
//检测错误就跳过
if (readCount == AudioTrack.ERROR_INVALID_OPERATION|| readCount == AudioTrack.ERROR_BAD_VALUE){
continue;
}
if (readCount != -1 && readCount != 0){
//可以在这个位置用play()
//输出音频数据
maudioTrack.write(buffer,0,readCount); //一次次的write输出播放
}
}
} catch (IOException e) {
e.printStackTrace();
}
Log.i("TAG","STREAM模式播放完成");
}
}.start();
}
}
//pcm数据的大小
byte[] databyte;
// static模式,需要将音频数据一次性write到AudioTrack的内部缓冲区
private void playPCM_STATIC() {
if (maudioTrack != null){
maudioTrack.stop();
maudioTrack.release();
maudioTrack = null;
}
new AsyncTask(){
@Override
protected Void doInBackground(Void... voids) {
try {
InputStream in = new FileInputStream(new File(mFileName));
ByteArrayOutputStream out = new ByteArrayOutputStream();
int b;
while((b = in.read()) != -1){
out.write(b);
}
//得到数据大小
databyte = out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
//创建AudioTrack.MODE_STATIC模式的AudioTrack
maudioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder()
.setSampleRate(mSampleRateInHz)
.setEncoding(mAudioFormat)
.setChannelMask(mChannelConfig)
.build(),
databyte.length,
AudioTrack.MODE_STATIC,AudioManager.AUDIO_SESSION_ID_GENERATE
);
//将数据 databyte 一次性写入AudioTrack,之后才能play
maudioTrack.write(databyte,0,databyte.length);
maudioTrack.play();
Log.i("TAG","STATIC模式播放完成");
}
}.execute();
}