1问题引入
- Android原生的AudioRecord没有自带的pause()方法(MediaRecord需要较高的版本才有,具体效果有待测试)。低版本的MediaRecord在通过stop()之类的方法暂停再继续录音,最后生成的多个音频文件如果没有通过ffmpeg合成,之前的断点会出现明显的爆音。
- 本文通过flag,去判断AudioRecord 的写入,绕过了Android添加的报头等无效数据。基本实现无爆音的暂停和续播。
- 其他:文件大小大概每5s 1M,录制的文件保存了txt的,然后转换成wav。之所以录制成pcm,还考虑到如果对音频进行处理的话(比如音效,伴奏,混音),pcm数据好处理,这部分研究后有机会再写吧 XD
2具体代码
- AudioTrackToRecordActivity
import android.app.Activity;
import android.content.Context;
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.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import zx.com.mytextproject.R;
/**
* 用audioRecord录制pcm 能够无间断暂停
* */
public class AudioTrackToRecordActivity extends Activity implements View.OnClickListener {
Context ctx;
Button btn_a2r_start;
Button btn_a2r_pause;
Button btn_a2r_stop;
Button btn_a2r_play;
Boolean isRecording = false;//是否开启了recorder
Boolean isPause = false;//是否正在暂停
Boolean isOver = false;//是否结束
//录制相关
AudioRecord audioRecord;
private int audioSource = MediaRecorder.AudioSource.MIC;// 音频获取源
private static int sampleRateInHz = 44100; // 设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025
private static int channelConfig = AudioFormat.CHANNEL_IN_STEREO;// 设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;// 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
private static int audioMode = AudioTrack.MODE_STREAM;
int RecordbufferSize;
//文件
File file;
File file2;//wav
String filename = null;
String filename2 = null;
MyThread recordT;
OutputStream outputStream;
//播放
AudioTrack audioTrack;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ctx = this;
setContentView(R.layout.activity_audiotracktorecord);
initView();
}
private void initView() {
btn_a2r_start = findViewById(R.id.btn_a2r_start);
btn_a2r_pause = findViewById(R.id.btn_a2r_pause);
btn_a2r_stop = findViewById(R.id.btn_a2r_stop);
btn_a2r_play = findViewById(R.id.btn_a2r_play);
btn_a2r_start.setOnClickListener(this);
btn_a2r_pause.setOnClickListener(this);
btn_a2r_stop.setOnClickListener(this);
btn_a2r_play.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_a2r_start:
if(isRecording){
if(isPause==false){
isPause = true;//停止录制
if(recordT!=null){
recordT.setStop(true);//再次停止线程
}
}
}
//清理
refreshState();
//初始化 启动
initRecordData();
isRecording = true;
break;
case R.id.btn_a2r_pause:
if(!isRecording||isOver){
Toast.makeText(this,"未在录制",Toast.LENGTH_SHORT).show();
return;
}
if(isPause){
isPause = false;
btn_a2r_pause.setText("暂停");
Toast.makeText(this,"继续录制中",Toast.LENGTH_SHORT).show();
WriteToFile();
}else{
isPause = true;
btn_a2r_pause.setText("继续录制");
Toast.makeText(this,"暂停中",Toast.LENGTH_SHORT).show();
}
break;
case R.id.btn_a2r_stop:
if(!isRecording){
Toast.makeText(this,"未在录制!isRecording",Toast.LENGTH_SHORT).show();
return;
}
if(isOver){
Toast.makeText(this,"未在录制isOver",Toast.LENGTH_SHORT).show();
return;
}
if(!isPause){
isPause = true;
if(recordT!=null){
recordT.setStop(true);//再次停止线程
}
}
audioRecord.stop();
audioRecord.release();
audioRecord = null;
isOver = true;
isRecording = false;
//合成
PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(sampleRateInHz,channelConfig,audioFormat);
pcmToWavUtil.pcmToWav(filename,filename2);
break;
case R.id.btn_a2r_play:
if(isOver){
//播放
Thread t = new Thread(new Runnable() {
@Override
public void run() {
playPcm();
}
});
t.start();
}else{
Toast.makeText(this,"还没有播放的文件",Toast.LENGTH_SHORT).show();
}
break;
}
}
/**
* 清理
* */
private void refreshState() {
isRecording = false;//是否开启了recorder
isPause = false;//是否正在暂停
isOver = false;//是否结束
recordT = null;
}
private void initRecordData(){
RecordbufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);// 获得缓冲区字节大小
audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, RecordbufferSize);// 创建AudioRecord对象
//创建文件
filename = ctx.getFilesDir().getAbsolutePath() + File.separator + "zzzrecord.txt";///data/user/0/zx.com.mytextproject/files/zzzrecord.aac
filename2 = ctx.getFilesDir().getAbsolutePath() + File.separator + "zzzrecord.wav";///data/user/0/zx.com.mytextproject/files/zzzrecord.aac
file = new File(filename);
file2 = new File(filename2);
Log.d("zzz", "filename == " + filename);
if (file.exists()) {
file.delete();
file2.delete();
Log.d("zzz", "删除文件");
}
try {
file.createNewFile();
} catch (IOException e) {
Log.i("zzz", "未能创建");
throw new IllegalStateException("未能创建" + file.toString());
}
audioRecord.startRecording();
WriteToFile();
}
private void WriteToFile() {
recordT = new MyThread();
recordT.start();
}
private void playPcm() {
int bufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig,audioFormat);// 获得音频缓冲区大小
audioTrack=new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig, audioFormat, bufferSize*2, AudioTrack.MODE_STREAM);
DataInputStream dis = null;
try {
// dis=new DataInputStream(new FileInputStream(filename));
dis=new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
byte[] data =new byte [bufferSize];
audioTrack.play();
while (true){
int i=0;
try {
while(dis.available()>0&&i<data.length)
{
data[i]=dis.readByte();//录音时write Byte 那么读取时就该为readByte要相互对应
i++;
}
} catch (IOException e) {
e.printStackTrace();
}
audioTrack.write(data,0,data.length);
if(i!=bufferSize) //表示读取完了
{
audioTrack.stop();//停止播放
audioTrack.release();//释放资源
break;
}
}
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 写入的类
* */
class MyThread extends Thread{
private volatile boolean isStop = false;
@Override
public void run() {
//在文本文本中追加内容
try {
outputStream = new FileOutputStream(filename,true) ; // 此处表示在文件末尾追加内容
} catch (Exception e) {
e.printStackTrace();
}
byte[] Recordbuffer = new byte[RecordbufferSize];//
int readLen;
while (!isPause&&!isStop) {
readLen = audioRecord.read(Recordbuffer, 0, Recordbuffer.length);
//录制
try {
outputStream.write(Recordbuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void setStop(boolean stop){
this.isStop = stop;
}
public boolean getStop(){
return isStop;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
isRecording = false;//是否开启了recorder
isPause = false;//是否正在暂停
isOver = false;//是否结束
if(recordT!=null){
recordT.setStop(true);//再次停止线程
}
if(audioRecord!=null){
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
recordT = null;
}
}
- activity_audiotracktorecord
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_a2r_start"
android:layout_width="match_parent"
android:layout_height="@dimen/y40"
android:text="开始录制"
/>
<Button
android:id="@+id/btn_a2r_pause"
android:layout_width="match_parent"
android:layout_height="@dimen/y40"
android:text="暂停"
/>
<Button
android:id="@+id/btn_a2r_stop"
android:layout_width="match_parent"
android:layout_height="@dimen/y40"
android:text="结束录制"
/>
<Button
android:id="@+id/btn_a2r_play"
android:layout_width="match_parent"
android:layout_height="@dimen/y40"
android:text="播放"
/>
</LinearLayout>
- PcmToWavUtil
import android.media.AudioFormat;
import android.media.AudioRecord;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 把录制的pcm转换成wav
*
* */
public class PcmToWavUtil {
/**
* 缓存的音频大小
*/
private int mBufferSize;
/**
* 采样率
*/
private int mSampleRate;
/**
* 声道数
*/
private int mChannel;
/**
* @param sampleRate sample rate、采样率
* @param channel channel、声道
* @param encoding Audio data format、音频格式
*/
PcmToWavUtil(int sampleRate, int channel, int encoding) {
this.mSampleRate = sampleRate;
this.mChannel = channel;
this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
}
/**
* pcm文件转wav文件
*
* @param inFilename 源文件路径
* @param outFilename 目标文件路径
*/
public void pcmToWav(String inFilename, String outFilename) {
FileInputStream in;
FileOutputStream out;
long totalAudioLen;
long totalDataLen;
long longSampleRate = mSampleRate;
int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
long byteRate = 16 * mSampleRate * channels / 8;
byte[] data = new byte[mBufferSize];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
writeWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 加入wav文件头
*/
private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
// RIFF/WAVE header
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
//WAVE
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// 'fmt ' chunk
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// 4 bytes: size of 'fmt ' chunk
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// format = 1
header[20] = 1;
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
// block align
header[32] = (byte) (2 * 16 / 8);
header[33] = 0;
// bits per sample
header[34] = 16;
header[35] = 0;
//data
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
}
最后别忘了权限等设置。亲测可用。
网友评论