行是知之始,知是行之成。这篇文章主要讲述Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能相关的知识,希望能为你提供帮助。
android音频处理——通过AudioRecord去保存PCM文件进行录制,
播放,
停止,
删除功能
音频这方面很博大精深, 我这里肯定讲不了什么高级的东西, 最多也只是一些基础类知识, 首先, 我们要介绍一下Android他提供的录音类,实际上他有两个, 一个是MediaRecorder, 还有一个就是我们今天要用到的AudioRecord, 那他们有什么区别呢?一.区别
MediaRecorder和AudioRecord都可以录制音频, 区别是MediaRecorder录制的音频文件是经过压缩后的, 需要设置编码器。并且录制的音频文件可以用系统自带的Music播放器播放。二.优缺点
而AudioRecord录制的是PCM格式的音频文件, 需要用AudioTrack来播放, AudioTrack更接近底层。
PCM可能更加可以理解为音频的源文件
- AudioRecord
主要是实现边录边播以及对音频的实时处理,这个特性让他更适合在语音方面有优势
优点: 语音的实时处理, 可以用代码实现各种音频的封装
缺点: 输出是PCM格式文件, 如果保存成音频文件, 是不能够被播放器播放的, 所以必须先写代码实现数据编码以及压缩
- MediaRecorder
已经集成了录音、编码、压缩等, 支持少量的录音音频格式, 大概有,aac,amr,3gp等三.准备工作
【Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能】优点: 集成, 直接调用相关接口即可, 代码量小
缺点: 无法实时处理音频; 输出的音频格式不是很多, 例如没有输出mp3格式文件
我们要实现的是一个实时的去录音, 播放, 停止等功能的测试案例, 那我们肯定要准备点什么, 比如说, 我这里先创建一个项目——PCMSamplelayout_main.xml
然后写个布局
<
?xml version=
"
1.0"
encoding=
"
utf-8"
?>
<
LinearLayout
xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:layout_width=
"
match_parent"
android:layout_height=
"
match_parent"
android:orientation=
"
vertical"
android:padding=
"
10dp"
>
<
Button
android:id=
"
@
+
id/startAudio"
android:layout_width=
"
match_parent"
android:layout_height=
"
wrap_content"
android:background=
"
@
drawable/button_bg"
android:text=
"
开始录音"
android:textColor=
"
@
android:color/white"
/>
<
Button
android:id=
"
@
+
id/stopAudio"
android:layout_width=
"
match_parent"
android:layout_height=
"
wrap_content"
android:layout_marginBottom=
"
10dp"
android:layout_marginTop=
"
5dp"
android:background=
"
@
drawable/button_bg"
android:enabled=
"
false"
android:text=
"
停止录音"
android:textColor=
"
@
android:color/white"
/>
<
Button
android:id=
"
@
+
id/playAudio"
android:layout_width=
"
match_parent"
android:layout_height=
"
wrap_content"
android:background=
"
@
drawable/button_bg"
android:enabled=
"
false"
android:text=
"
播放音频"
android:textColor=
"
@
android:color/white"
/>
<
Button
android:id=
"
@
+
id/deleteAudio"
android:layout_width=
"
match_parent"
android:layout_height=
"
wrap_content"
android:layout_marginTop=
"
5dp"
android:background=
"
@
drawable/button_bg"
android:text=
"
删除PCM"
android:textColor=
"
@
android:color/white"
/>
<
ScrollView
android:id=
"
@
+
id/mScrollView"
android:layout_width=
"
match_parent"
android:layout_height=
"
0dp"
android:layout_marginTop=
"
5dp"
android:layout_weight=
"
1"
>
<
TextView
android:id=
"
@
+
id/tv_audio_succeess"
android:layout_width=
"
wrap_content"
android:layout_height=
"
wrap_content"
android:text=
"
初始化完成...."
android:textColor=
"
@
color/colorAccent"
/>
<
/ScrollView>
<
/LinearLayout>
可以预览一下
文章图片
这里我给按钮加了一个扁平的效果, 实际上写了一个xml, 很简单button_bg.xml
<
?xml version=
"
1.0"
encoding=
"
utf-8"
?>
<
selector xmlns:android=
"
http://schemas.android.com/apk/res/android"
>
<
item android:state_pressed=
"
true"
>
<
shape>
<
corners android:radius=
"
30dp"
/>
<
solid android:color=
"
@
color/colorPrimary"
/>
<
/shape>
<
/item>
<
item android:state_pressed=
"
false"
>
<
shape>
<
corners android:radius=
"
30dp"
/>
<
solid android:color=
"
@
color/colorPrimaryDark"
/>
<
/shape>
<
/item>
<
/selector>
好的, 回到正题, 我们这里有四个按钮, 分别是开始。停止, 播放, 和删除, 我们就是要实现这四个功能, 在此之前, 我们还需要做的事情就是添加权限, 因为我们要录音和写内存卡文件, 所有需要这两个权限即可
<
!--录音-->
<
uses-permission android:name=
"
android.permission.RECORD_AUDIO"
/>
<
!--读取SD卡-->
<
uses-permission android:name=
"
android.permission.WRITE_EXTERNAL_STORAGE"
/>
这里初始化什么的就不说了, 我们直接进入正题四.开始录音
开始录音的话, 这里, 我们定义一个变量isRecording去控制, 这样就比较好结束了, 而且要注意的是, 录音是不能放在UI线程的, 你懂的, 所以我们可以写一个开始录音的方法
//开始录音
public void StartRecord() {
Log.i(TAG,"
开始录音"
);
//16K采集率
int frequency =
16000;
//格式
int channelConfiguration =
AudioFormat.CHANNEL_CONFIGURATION_MONO;
//16Bit
int audioEncoding =
AudioFormat.ENCODING_PCM_16BIT;
//生成PCM文件
file =
new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
"
/reverseme.pcm"
);
Log.i(TAG,"
生成文件"
);
//如果存在,
就先删除再创建
if (file.exists())
file.delete();
Log.i(TAG,"
删除文件"
);
try {
file.createNewFile();
Log.i(TAG,"
创建文件"
);
} catch (IOException e) {
Log.i(TAG,"
未能创建"
);
throw new IllegalStateException("
未能创建"
+
file.toString());
}
try {
//输出流
OutputStream os =
new FileOutputStream(file);
BufferedOutputStream bos =
new BufferedOutputStream(os);
DataOutputStream dos =
new DataOutputStream(bos);
int bufferSize =
AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
AudioRecord audioRecord =
new AudioRecord(MediaRecorder.Audiosource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize);
short[] buffer =
new short[bufferSize];
audioRecord.startRecording();
Log.i(TAG, "
开始录音"
);
isRecording =
true;
while (isRecording) {
int bufferReadResult =
audioRecord.read(buffer, 0, bufferSize);
for (int i =
0;
i <
bufferReadResult;
i+
+
) {
dos.writeShort(buffer[i]);
}
}
audioRecord.stop();
dos.close();
} catch (Throwable t) {
Log.e(TAG, "
录音失败"
);
}
}
首先, 这里我们了解一下采样率, 编码, 音频流等基本的概念, 剩下的大多是读写流的操作了, 我们通过创建一个AudioRecord去写pcm文件, 定义一个while循环, 用我们刚才定义的isRecording控制, 所以, 我们的点击事件就
case R.id.startAudio:
Thread thread =
new Thread(new Runnable() {
@
Override
public void run() {
StartRecord();
Log.e(TAG,"
start"
);
}
});
thread.start();
printLog("
开始录音"
);
ButtonEnabled(false, true, false);
break;
这里要注意一下thread.start(); 开启线程, 同时打印出log, 具体代码如下
//打印log
private void printLog(final String resultString) {
tv_audio_succeess.post(new Runnable() {
@
Override
public void run() {
tv_audio_succeess.append(resultString +
"
\\n"
);
mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
这里, 我为了防止ANR, 所以控制了一下按钮的焦点
//获取/失去焦点
private void ButtonEnabled(boolean start, boolean stop, boolean play) {
startAudio.setEnabled(start);
stopAudio.setEnabled(stop);
playAudio.setEnabled(play);
}
好的, 我们运行一下
文章图片
看起来没什么变化, 但是你去内存卡中就会发现多了一个pcm文件
文章图片
当然, 你只是点击启动录音是不会生成这个pcm文件的, 你需要点击停止停止录音的按钮五.停止录音
停止录音很简单, 我们控制通过改变写入流就好了
case R.id.stopAudio:
isRecording =
false;
ButtonEnabled(true, false, true);
printLog("
停止录音"
);
break;
这样才会生成PCM六播放音频
现在有了PCM我们可以试着去播放了, 写一个播放的方法
//播放文件
public void PlayRecord() {
if(file =
=
null){
return;
}
//读取文件
int musicLength =
(int) (file.length() / 2);
short[] music =
new short[musicLength];
try {
InputStream is =
new FileInputStream(file);
BufferedInputStream bis =
new BufferedInputStream(is);
DataInputStream dis =
new DataInputStream(bis);
int i =
0;
while (dis.available() >
0) {
music[i] =
dis.readShort();
i+
+
;
}
dis.close();
AudioTrack audioTrack =
new AudioTrack(AudioManager.STREAM_MUSIC,
16000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT,
musicLength * 2,
AudioTrack.MODE_STREAM);
audioTrack.play();
audioTrack.write(music, 0, musicLength);
audioTrack.stop();
} catch (Throwable t) {
Log.e(TAG, "
播放失败"
);
}
}
正如上面所说, 我们播放需要用到AudioTrack, 调用他的play方法以及设置一些参数即可七.删除音频
删除音频只需要删除这个pcm文件就行
//删除文件
private void deleFile() {
if(file =
=
null){
return;
}
file.delete();
printLog("
文件删除成功"
);
}
这就是大致的录音逻辑, 虽然看起来很简单, 但是这正是现在很多语音和音频的最基础部分, 特别是语音, 如果你从事语音的工作, 我相信你会感谢我的!MainActivity
好了, 最后放上完整的代码:
package com.liuguilin.pcmsample;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {public static final String TAG =
"
PCMSample"
;
//是否在录制
private boolean isRecording =
false;
//开始录音
private Button startAudio;
//结束录音
private Button stopAudio;
//播放录音
private Button playAudio;
//删除文件
private Button deleteAudio;
private ScrollView mScrollView;
private TextView tv_audio_succeess;
//pcm文件
private File file;
@
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}//初始化View
private void initView() {mScrollView =
(ScrollView) findViewById(R.id.mScrollView);
tv_audio_succeess =
(TextView) findViewById(R.id.tv_audio_succeess);
printLog("
初始化成功"
);
startAudio =
(Button) findViewById(R.id.startAudio);
startAudio.setOnClickListener(this);
stopAudio =
(Button) findViewById(R.id.stopAudio);
stopAudio.setOnClickListener(this);
playAudio =
(Button) findViewById(R.id.playAudio);
playAudio.setOnClickListener(this);
deleteAudio =
(Button) findViewById(R.id.deleteAudio);
deleteAudio.setOnClickListener(this);
}//点击事件
@
Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.startAudio:
Thread thread =
new Thread(new Runnable() {
@
Override
public void run() {
StartRecord();
Log.e(TAG,"
start"
);
}
});
thread.start();
printLog("
开始录音"
);
ButtonEnabled(false, true, false);
break;
case R.id.stopAudio:
isRecording =
false;
ButtonEnabled(true, false, true);
printLog("
停止录音"
);
break;
case R.id.playAudio:
PlayRecord();
ButtonEnabled(true, false, false);
printLog("
播放录音"
);
break;
case R.id.deleteAudio:
deleFile();
break;
}
}//打印log
private void printLog(final String resultString) {
tv_audio_succeess.post(new Runnable() {
@
Override
public void run() {
tv_audio_succeess.append(resultString +
"
\\n"
);
mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}//获取/失去焦点
private void ButtonEnabled(boolean start, boolean stop, boolean play) {
startAudio.setEnabled(start);
stopAudio.setEnabled(stop);
playAudio.setEnabled(play);
}//开始录音
public void StartRecord() {
Log.i(TAG,"
开始录音"
);
//16K采集率
int frequency =
16000;
//格式
int channelConfiguration =
AudioFormat.CHANNEL_CONFIGURATION_MONO;
//16Bit
int audioEncoding =
AudioFormat.ENCODING_PCM_16BIT;
//生成PCM文件
file =
new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
"
/reverseme.pcm"
);
Log.i(TAG,"
生成文件"
);
//如果存在,
就先删除再创建
if (file.exists())
file.delete();
Log.i(TAG,"
删除文件"
);
try {
file.createNewFile();
Log.i(TAG,"
创建文件"
);
} catch (IOException e) {
Log.i(TAG,"
未能创建"
);
throw new IllegalStateException("
未能创建"
+
file.toString());
}
try {
//输出流
OutputStream os =
new FileOutputStream(file);
BufferedOutputStream bos =
new BufferedOutputStream(os);
DataOutputStream dos =
new DataOutputStream(bos);
int bufferSize =
AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
AudioRecord audioRecord =
new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize);
short[] buffer =
new short[bufferSize];
audioRecord.startRecording();
Log.i(TAG, "
开始录音"
);
isRecording =
true;
while (isRecording) {
int bufferReadResult =
audioRecord.read(buffer, 0, bufferSize);
for (int i =
0;
i <
bufferReadResult;
i+
+
) {
dos.writeShort(buffer[i]);
}
}
audioRecord.stop();
dos.close();
} catch (Throwable t) {
Log.e(TAG, "
录音失败"
);
}
}//播放文件
public void PlayRecord() {
if(file =
=
null){
return;
}
//读取文件
int musicLength =
(int) (file.length() / 2);
short[] music =
new short[musicLength];
try {
InputStream is =
new FileInputStream(file);
BufferedInputStream bis =
new BufferedInputStream(is);
DataInputStream dis =
new DataInputStream(bis);
int i =
0;
while (dis.available() >
0) {
music[i] =
dis.readShort();
i+
+
;
}
dis.close();
AudioTrack audioTrack =
new AudioTrack(AudioManager.STREAM_MUSIC,
16000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT,
musicLength * 2,
AudioTrack.MODE_STREAM);
audioTrack.play();
audioTrack.write(music, 0, musicLength);
audioTrack.stop();
} catch (Throwable t) {
Log.e(TAG, "
播放失败"
);
}
}//删除文件
private void deleFile() {
if(file =
=
null){
return;
}
file.delete();
printLog("
文件删除成功"
);
}}
如果你想去调试这些pcm文件做音频测试的话, 我推荐使用Audacity这个软件, 可以看到, 我直接点击左上角的file-导入-源文件, 然后设置16K
文章图片
这样就可以调试了
文章图片
最后, 放一张完整的截图
文章图片
嗯, 这篇文章就到这里, 还是有些基础了, 希望下次能给大家带来高深点的文章吧, 有兴趣的加群: 555974449Sample下载: http://download.csdn.net/detail/qq_26787115/9676003
推荐阅读
- android透明度渐变动画
- 对自己开发的产品负责——《腾讯Android自动化测试实战》
- Android相机开发那些坑
- [转]查看Android源码版本
- 安卓必备,HttpUrlConnection
- Android 优秀的开源框架整理
- Android 热补丁实践之路
- Android7.0 Ninja编译原理
- 深入Android内存泄露