前几篇的文章都是camera下采集视频数据进行显示,保存下来的文件也是h264格式的,并无包含音频数据,因此多多少少有点单调的感受。没有声音的视频是没有灵魂的,因此最近了解了一下音频相关的开发,给视频注入灵魂。java
开始音频学习以前,有必要先了解一下基础知识,由于在音频开发过程当中,常常会涉及到这些。掌握了这些重要的概念,在学习中不少参数的配置会更容易理解。android
PCM编码格式ide
首先看看百度百科给出的解释:PCM 脉冲编码调制是Pulse Code Modulation的缩写。脉冲编码调制是数字通讯的编码方式之一。主要过程是将话音、图像等模拟信号每隔必定时间进行取样,使其离散化,同时将抽样值按分层单位四舍五入取整量化,同时将抽样值按一组二进制码来表示抽样脉冲的幅值。 这个解释得很是抽象,反正我是没看懂⊙﹏⊙。简单的来讲就是将声音数字化,转换为二进制序列,这样就能够把声音保存下来了,而保存它的容器能够是mp3,wav等等容器。函数
音频采集输入源学习
这个就至关于声明孩子他妈是谁,也就是说声音的源头在哪儿。可选的类型以常量的形式定义在 MediaRecorder.AudioSource 类中 ,比较经常使用的是下面几个this
采样率编码
咱们把采样到的一个个静止画面再以采样率一样的速度回放时,看到的就是连续的画面。一样的道理,把以44.1kHZ采样率记录的CD以一样的速率播放时,就能听到连续的声音。显然,这个采样率越高,听到的声音和看到的图像就越连贯。固然,人的听觉和视觉器官能分辨的采样率是有限的,基本上高于44.1kHZ采样的声音,绝大部分人已经觉察不到其中的分别了。 而目前44100Hz是惟一能够保证兼容全部Android手机的采样率 。因此,若是不是特殊设备和用途,这个值建议设置为44100。spa
通道数线程
声道数通常表示声音录制时的音源数量或回放时相应的扬声器数量。经常使用的有:单通道和双通道。 可选的值以常量的形式定义在 AudioFormat 类中,经常使用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)code
量化精度
帧间隔
音频不像视频那样,有一帧一帧的概念。它是约定一个时间为单位,而后这个时间内的数据为一帧,这个时间被称为采样时间。这个时间没有特别的标准,要看具体的编解码器。
了解音频开发相关基础知识以后,咱们就能够开始使用android提供的相关API实现音频的录制了,android提供了两套音频录制的API:
MediaRecorder
:比较上层的 API
,它能够直接把手机麦克风的音频数据进行编码而后储存成文件。使用简单,可是支持的格式有限,而且不支持对音频进行进一步的处理,例如变声、混音等。AudioRecord
:比较底层的一个 API
,可以获得原始的 PCM
音频数据。因为咱们获得的是原始的 PCM
数据,咱们能够对音频进行进一步的处理,例如编码、混音和变声等。这里主要介绍的是使用AudioRecord
进行录制,MediaRecorder
的录制比较简单,就不作过多介绍了。首先看看AudioRecord的构造函数:
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes) 复制代码
须要传入5个参数,分别是输入源,采样率,通道数,量化精度,音频缓冲区的大小 。其中最后一个也是最重要的一个参数,它表明音频缓冲区的大小,该缓冲区的值不能低于一帧音频帧的大小,
一帧音频帧大小 = 采样率 x 位宽 x 采样时间 x 通道数 ,这个值不用咱们本身计算,AudioRecord 类提供了一个帮助你肯定这个值的函数 :
public static int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) 复制代码
当建立好AudioRecord对象以后,就能够开始进行音频数据的采集了,控制采集的开始和中止的方法是下面这两个函数:
public void startRecording() public void stop() 复制代码
开始采集以后,经过线程循环取走音频数据:
public int read(byte[] audioData, int offsetInBytes, int sizeInBytes) 复制代码
最终录制的代码以下,注意该方法须要在子线程中运行:
private File mAudioFile;
private FileOutputStream mAudioFileOutput;
private boolean isRecording = false;
private int sampleRate = 44100;//全部android系统都支持 采样率
//单声道输入
private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
//PCM_16是全部android系统都支持的 16位的声音就是人类能听到的极限了,再高就听不见了 位数越高声音越清晰
private int autioFormat = AudioFormat.ENCODING_PCM_16BIT;
private int recordBufSize = 0; // 声明recoordBufffer的大小字段
private AudioRecord audioRecord = null;
private boolean startAudioRecord(String fileName) {
isRecording = true;
mAudioFile = new File(mPath+fileName+System.currentTimeMillis()+".pcm");
if (!mAudioFile.getParentFile().exists()){
mAudioFile.getParentFile().mkdirs();
}
try {
mAudioFile.createNewFile();
//建立文件输出流
mAudioFileOutput = new FileOutputStream(mAudioFile);
//计算audioRecord能接受的最小的buffer大小
recordBufSize = AudioRecord.getMinBufferSize(sampleRate,
channelConfig,
autioFormat);
Log.e(TAG, "最小的buffer大小: " + recordBufSize);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
sampleRate,
channelConfig,
autioFormat, recordBufSize);
//初始化一个buffer 用于从AudioRecord中读取声音数据到文件流
byte data[] = new byte[recordBufSize];
//开始录音
audioRecord.startRecording();
while (isRecording){
//只要还在录音就一直读取
int read = audioRecord.read(data,0,recordBufSize);
if (read > 0){
mAudioFileOutput.write(data,0,read);
}
}
//stopRecorder();
} catch (IOException e) {
e.printStackTrace();
stopAudioRecord();
return false;
}
return true;
}
public boolean stopAudioRecord(){
isRecording = false;
if (audioRecord != null){
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
try {
mAudioFileOutput.flush();
mAudioFileOutput.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
复制代码
播放pcm格式的音频和录制不一样的地方有三个:
一个是建立AudioTrack对象,和AudioRecord的构造方法不一样,第一个参数是音乐类型,AudioManager.STREAM_MUSIC表示用扬声器播放,最后多出的一个参数是播放模式,通常使用AudioTrack.MODE_STREAM,适用于大多数的场景,将audio buffers从java层传递到native层即返回。 若是audio buffers占用内存多,应该使用MODE_STREAM。 好比播放时间很长的声音文件, 好比音频文件使用高采样率等:
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) 复制代码
另外两个是把录制的startRecording()和read()方法换成下面两个:
public void play() public int write(byte[] audioData, int offsetInBytes, int sizeInBytes) 复制代码
最终播放pcm的代码以下:
private FileInputStream mAudioPlayInputStream;
public void doPlay(File audioFile) {
if (audioFile.isDirectory() || !audioFile.exists()) return;
mIsPlaying = true;
//配置播放器
//音乐类型,扬声器播放
int streamType= AudioManager.STREAM_MUSIC;
//录音时采用的采样频率,因此播放时一样的采样频率
int sampleRate=44100;
//单声道,和录音时设置的同样
int channelConfig=AudioFormat.CHANNEL_OUT_MONO;
//录音时使用16bit,因此播放时一样采用该方式
int audioFormat=AudioFormat.ENCODING_PCM_16BIT;
//流模式
int mode= AudioTrack.MODE_STREAM;
//计算最小buffer大小
int minBufferSize = AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);
byte data[] = new byte[minBufferSize];
//构造AudioTrack 不能小于AudioTrack的最低要求,也不能小于咱们每次读的大小
mAudioTrack = new AudioTrack(streamType,sampleRate,channelConfig,audioFormat,
Math.max(minBufferSize,data.length),mode);
//从文件流读数据
try{
//循环读数据,写到播放器去播放
mAudioPlayInputStream = new FileInputStream(audioFile);
//循环读数据,写到播放器去播放
int read;
//只要没读完,循环播放
mAudioTrack.play();
while (mIsPlaying){
int ret = 0;
read = mAudioPlayInputStream.read(data);
if (read > 0){
ret = mAudioTrack.write(data,0,read);
}
//mAudioFileOutput.write(data,0,read);
//检查write的返回值,处理错误
switch (ret){
case AudioTrack.ERROR_INVALID_OPERATION:
case AudioTrack.ERROR_BAD_VALUE:
case AudioManager.ERROR_DEAD_OBJECT:
Log.d(TAG, "doPlay: 失败,错误码:"+ret);
return;
default:
break;
}
}
}catch (Exception e){
e.printStackTrace();
//读取失败
Log.d(TAG, "doPlay: 失败");
}finally {
stopPlay();
Log.d(TAG, "结束播放");
}
}
public void stopPlay() {
mIsPlaying = false;
//播放器释放
if (mAudioTrack != null) {
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
//关闭文件输入流
if (mAudioPlayInputStream != null) {
try {
mAudioPlayInputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
复制代码
最后贴上全部的代码:
public class AudioUtil {
private static final String TAG = "AudioUtil";
private AudioRecord audioRecord = null; // 声明 AudioRecord 对象
private int recordBufSize = 0; // 声明recoordBufffer的大小字段
//全部android系统都支持 采样率:采样率越高,听到的声音和看到的图像就越连贯
// 基本上高于44.1kHZ采样的声音,绝大部分人已经觉察不到其中的分别了
private int sampleRate = 44100;
//单声道输入
private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
//PCM_16是全部android系统都支持的 16位的声音就是人类能听到的极限了,再高就听不见了 位数越高声音越清晰
private int autioFormat = AudioFormat.ENCODING_PCM_16BIT;
private long mStartTimeStamp;
private File mAudioFile;
private String mPath = ContentValue.MAIN_PATH + "/AudioSimple/";
private FileOutputStream mAudioFileOutput; //存储录音文件
private FileInputStream mAudioPlayInputStream; //播放录音文件
private boolean isRecording = false;
private static AudioUtil audioUtil;
private boolean mIsPlaying;
private AudioTrack mAudioTrack;
private String mRecordFileName; //录音保存的文件路径
private String mPlayFileName; //播放录音的文件路径
private Runnable mAudioRunnableTask = new Runnable() {
@Override
public void run() {
boolean result = startAudioRecord(mRecordFileName);
if (result){
Log.e(TAG, "录音结束");
}else {
Log.e(TAG, "录音失败");
}
}
};
private Runnable mAudioPlayRunnableTask = new Runnable() {
@Override
public void run() {
File file = new File(mPlayFileName);
doPlay(file);
}
};
private AudioUtil(){}
public static AudioUtil getInstance(){
if (audioUtil == null){
synchronized (AudioUtil.class){
if (audioUtil == null){
audioUtil = new AudioUtil();
}
}
}
return audioUtil;
}
private AudioEncoder mAudioEncoder;
private boolean startAudioRecord(String fileName) {
isRecording = true;
mStartTimeStamp = System.currentTimeMillis();
mAudioFile = new File(mPath+fileName+mStartTimeStamp+".pcm");
if (!mAudioFile.getParentFile().exists()){
mAudioFile.getParentFile().mkdirs();
}
try {
mAudioFile.createNewFile();
//建立文件输出流
mAudioFileOutput = new FileOutputStream(mAudioFile);
//计算audioRecord能接受的最小的buffer大小
recordBufSize = AudioRecord.getMinBufferSize(sampleRate,
channelConfig,
autioFormat);
Log.e(TAG, "最小的buffer大小: " + recordBufSize);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
sampleRate,
channelConfig,
autioFormat, recordBufSize);
//初始化一个buffer 用于从AudioRecord中读取声音数据到文件流
byte data[] = new byte[recordBufSize];
//开始录音
audioRecord.startRecording();
while (isRecording){
//只要还在录音就一直读取
int read = audioRecord.read(data,0,recordBufSize);
if (read > 0){
mAudioFileOutput.write(data,0,read);
}
}
//stopRecorder();
} catch (IOException e) {
e.printStackTrace();
stopAudioRecord();
return false;
}
return true;
}
public void doPlay(File audioFile) {
if(audioFile !=null){
mIsPlaying = true;
//配置播放器
//音乐类型,扬声器播放
int streamType= AudioManager.STREAM_MUSIC;
//录音时采用的采样频率,因此播放时一样的采样频率
int sampleRate=44100;
//单声道,和录音时设置的同样
int channelConfig=AudioFormat.CHANNEL_OUT_MONO;
//录音时使用16bit,因此播放时一样采用该方式
int audioFormat=AudioFormat.ENCODING_PCM_16BIT;
//流模式
int mode= AudioTrack.MODE_STREAM;
//计算最小buffer大小
int minBufferSize = AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);
byte data[] = new byte[minBufferSize];
//构造AudioTrack 不能小于AudioTrack的最低要求,也不能小于咱们每次读的大小
mAudioTrack = new AudioTrack(streamType,sampleRate,channelConfig,audioFormat,
Math.max(minBufferSize,data.length),mode);
//从文件流读数据
try{
//循环读数据,写到播放器去播放
mAudioPlayInputStream = new FileInputStream(audioFile);
//循环读数据,写到播放器去播放
int read;
//只要没读完,循环播放
mAudioTrack.play();
while (mIsPlaying){
int ret = 0;
read = mAudioPlayInputStream.read(data);
if (read > 0){
ret = mAudioTrack.write(data,0,read);
}
//mAudioFileOutput.write(data,0,read);
//检查write的返回值,处理错误
switch (ret){
case AudioTrack.ERROR_INVALID_OPERATION:
case AudioTrack.ERROR_BAD_VALUE:
case AudioManager.ERROR_DEAD_OBJECT:
Log.d(TAG, "doPlay: 失败,错误码:"+ret);
return;
default:
break;
}
}
}catch (Exception e){
e.printStackTrace();
//读取失败
Log.d(TAG, "doPlay: 失败");
}finally {
stopPlay();
Log.d(TAG, "结束播放");
}
}
}
public void startRecord(String fileName){
this.mRecordFileName = fileName;
new Thread(mAudioRunnableTask).start();
}
public void startPlay(String fileName){
this.mPlayFileName = fileName;
new Thread(mAudioPlayRunnableTask).start();
}
public boolean stopAudioRecord(){
isRecording = false;
if (audioRecord != null){
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
if (mAudioEncoder != null){
mAudioEncoder.stopEncodeAac();
}
try {
mAudioFileOutput.flush();
mAudioFileOutput.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
public boolean isRecording(){
return isRecording;
}
public boolean isPlaying(){
return mIsPlaying;
}
public void stopPlay(){
mIsPlaying = false;
//播放器释放
if(mAudioTrack != null){
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
//关闭文件输入流
if(mAudioPlayInputStream !=null){
try {
mAudioPlayInputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
private AudioListener mAudioListener;
public void setAudioListener(AudioListener listener){
this.mAudioListener = listener;
}
public interface AudioListener{
void onRecordFinish();
void onPlayFinish();
}
}
复制代码