Android提供了AudioRecorder和MediaRecorder两种方法录音,这里介绍AudioRecorder录音的方式,项目需求场景往往需要我们能够长时间录音,并且不影响在一个App内的其他操作,显然,Service是合适的选择,为了能够录出mp3格式的录音,我们需要引入Lame库,边录边转码为mp3格式,好了,先上截图,再上代码.
1.gradle配置,并将下载好的.so文件放在jniLibs/armeabi目录下
sourceSets {//将so库地址指定为libs目录下
main {
jniLibs.srcDirs = ['libs']
}
}
2.录音核心类Mp3Recorder,initAudioRecorder(),并在录音过程中把录音文件转码为mp3格式:
初始化AudioRecord
private void initAudioRecorder() throws IOException {
int bytesPerFrame = audioFormat.getBytesPerFrame();
/* Get number of samples. Calculate the buffer size (round up to the
factor of given frame size) */
int frameSize = AudioRecord.getMinBufferSize(samplingRate,
channelConfig, audioFormat.getAudioFormat())
/ bytesPerFrame; //返回字节为单位的创建AudioRecord的最小缓冲区大小
if (frameSize % FRAME_COUNT != 0) {
frameSize = frameSize + (FRAME_COUNT - frameSize % FRAME_COUNT); //设置frameSize为160的整数倍
Log.d(TAG, "Frame size: " + frameSize);
}
bufferSize = frameSize * bytesPerFrame; //录制过程中写入的缓冲区的总大小
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
samplingRate, channelConfig, audioFormat.getAudioFormat(),
bufferSize); //初始化AudioRecord
// 设置buffer为缓冲区的10倍大小
// 初始化buffer接受数据
ringBuffer = new RingBuffer(10 * bufferSize);
buffer = new byte[bufferSize];
// Initialize lame buffer
// mp3 sampling rate is the same as the recorded pcm sampling rate
// 初始化lame samplingRate采样率,1为声道数,BIT_RATE为比特率
SimpleLame.init(samplingRate, 1, samplingRate, BIT_RATE);
mp3File = new File(filePath);
if (!mp3File.getParentFile().exists()) {
mp3File.mkdirs();
}
os = new FileOutputStream(mp3File);
// 创建一个线程编码数据
encodeThread = new DataEncodeThread(ringBuffer, os, bufferSize);
encodeThread.start();
//设置audioRecord的监听并且设置监听
audioRecord.setRecordPositionUpdateListener(encodeThread, encodeThread.getHandler());
audioRecord.setPositionNotificationPeriod(FRAME_COUNT);
}
开始录音
public void startRecording() throws IOException {
if (isRecording) return;
Log.d(TAG, "Start recording");
Log.d(TAG, "BufferSize = " + bufferSize);
// Initialize audioRecord if it's null.
if (audioRecord == null) {
length = 0;
stopTime = 0;
startTime = 0;
initAudioRecorder();
}
audioRecord.startRecording();
startTime = new Date().getTime();
new Thread() {
@Override
public void run() {
isRecording = true;
while (isRecording) {
int bytes = audioRecord.read(buffer, 0,
bufferSize);//读取音频数据至buffer中(读取缓冲区(在过度运行尚未读取读取的数据)的数据至buffer)
if (bytes > 0) {
ringBuffer.write(buffer, bytes);//写入RingBuffer数据中
calculateRealVolume(buffer, bytes);
}
}
}
}.start();
}
停止录音
public void stopRecording() throws IOException {
Log.d(TAG, "stop recording");
isRecording = false;
calaTime();
// release and finalize audioRecord
try {
//停止录制
if (audioRecord != null) {
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
// stop the encoding thread and try to wait
// until the thread finishes its job(发送消息结束工作录制)
Message msg = Message.obtain(encodeThread.getHandler(),
DataEncodeThread.PROCESS_STOP);
msg.sendToTarget();
Log.d(TAG, "waiting for encoding thread");
encodeThread.join();//执行encodeThread线程
if (listener != null) {
listener.stop();
}
Log.d(TAG, "done encoding thread");
} catch (InterruptedException e) {
Log.d(TAG, "Faile to join encode thread");
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
音量计算及回调
private void calculateRealVolume(byte[] buffer, int readSize) {
double sum = 0;
for (int i = 0; i < readSize; i++) {
sum += buffer[i] * buffer[i];
}
if (readSize > 0) {
double amplitude = sum / readSize;
mVolume = (int) Math.sqrt(amplitude);
}
if (mVolume >= MAX_VOLUME) {
listener.Volume(MAX_VOLUME);
}else {
listener.Volume(mVolume);
}
}
3.Service中的使用,实现mp3Recorder接口
/**
* 开始录音
*/
public boolean startRecord() throws IOException {
// 如果正在录音,则返回
if (isRecording) {
return isRecording;
}
isRecording = true;
startTime = System.currentTimeMillis();
if (mp3Recorder == null) {
String fileName = TimeUtils.getCurrentTime();
mp3Path = mp3Floder + fileName + ".mp3";
mp3Recorder = new Mp3Recorder(mp3Path);
mp3Recorder.setListener(this);
mp3Recorder.startRecording();
Toast.makeText(this, "开始录音!", Toast.LENGTH_SHORT).show();
} else {
mp3Recorder.startRecording();
Toast.makeText(this, "正在录音!", Toast.LENGTH_SHORT).show();
}
setFlag(true);
return isRecording;
}
/**
* 停止录音
*/
public boolean stopRecordingAndConvertFile() throws IOException {
if (!isRecording) {
return isRecording;
}
isRecording = false;
mp3Recorder.stopRecording();
setFlag(false);
audioStatusUpdateListener.onStop(mp3Path);
//通知媒体库更新文件夹
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(new File(mp3Path));
intent.setData(uri);
this.sendBroadcast(intent);
mp3Recorder = null;
return isRecording;// convertOk==true,return true
}
//音量监听回调
@Override
public void Volume(final int volume) {
Message msg = new Message();
msg.what = 1;
msg.obj = volume;
mHandler.sendMessageDelayed(msg, 100);
}
4.Activity中绑定和使用
//绑定
intent = new Intent(AudioMp3Activity.this, AudioMp3RecoderService3.class);
bindService(intent, conn, Context.BIND_AUTO_CREATE);
//按钮录音开始和结束控制
if (RECORD_FLAG == 1) {
isRecording = true;
startService(intent);
try {
audioMp3RecordService.startRecord();
} catch (Exception e) {
e.printStackTrace();
}
RECORD_FLAG = 2;
break;
} else if (RECORD_FLAG == 2) {
try {
audioMp3RecordService.stopRecordingAndConvertFile();
} catch (IOException e) {
e.printStackTrace();
}
stopService(intent);
isRecording = false;
RECORD_FLAG = 1;
break;
}
9月14日更新
今天调试发现录音功能这个so库只能支持到SDK22,23以上会报出异常,所以需要更新so库
//TODO 更新so库以支持SDK23及以上
好了,基本到这,代码戳https://github.com/willShuhuan/MyDemos