最近一个需求,要求通话保证双向录音。目前相关文章比较少,于是去调研了相关的一些软件。唉~果然,又是一个不好做的坑位。IOS直接没法做。先说一下,如果用户不手动打开录音,只能简单的录单边。所以有类似需求的,不要想太多,这个侵犯隐私的未来会跟IOS一样彻底封禁。
先说一下我的思路,既然一般方案不行,那就换个角度去想问题。首先我们先说关于用户手动打开录音功能:
一:首先让用户去拨打电话设置页面-打开所有通话录音
二:让用户在拨打出电话后,点击功能,然后录音
然后我们根据各个手机厂商录音文件夹下找相关录音文件,所有的录音文件是包含你拨打的手机号的。相关代码:
public class AotuVoiceFileUtils {
static List<String> pathList = new ArrayList<>();
public static File pathFile() {
String parentPath = Environment.getExternalStorageDirectory().getAbsolutePath();
File childFile = null;
if (DeviceUtils.getDeviceManufacturer().equals("HUAWEI") || DeviceUtils.getDeviceManufacturer().equals("HONOR")) {
if (FileUtils.isExist(parentPath + "/Sounds/CallRecord")) {
childFile = new File(parentPath + "/Sounds/CallRecord");
} else if (FileUtils.isExist(parentPath + "/record")) {
childFile = new File(parentPath + "record");
} else if (FileUtils.isExist(parentPath + "/Record")) {
childFile = new File(parentPath + "/Record");
} else {
childFile = new File("");
}
} else if (DeviceUtils.getDeviceManufacturer().equals("XIAOMI")) {
childFile = new File(parentPath + "/MIUI/sound_recorder/call_rec/");
} else if (DeviceUtils.getDeviceManufacturer().equals("MEIZU")) {
childFile = new File(parentPath + "/Recorder");
} else if (DeviceUtils.getDeviceManufacturer().equals("OPPO")) {
childFile = new File(parentPath + "/Recordings");
} else if (DeviceUtils.getDeviceManufacturer().equals("VIVO")) {
childFile = new File(parentPath + "/Record/Call");
} else if (DeviceUtils.getDeviceManufacturer().equals("SAMSUNG")) {
childFile = new File(parentPath + "/Sounds");
} else {
childFile = new File("");
}
return childFile;
}
/*传入拨打的电话号码做匹配*/
public static String getPathList(String phone) {
ArrayList<String> result = new ArrayList<>();
File[] files = pathFile().listFiles();
if (files == null) {
return "";
}
for (int i = 0; i < files.length; i++) {
if (!files[i].isDirectory()) {
String fileName = files[i].getName();
if (fileName.trim().toLowerCase().contains(phone)) {
result.add(fileName);
}
}
}
return result.isEmpty() ? "" : result.get(0);
}
}
业务需求,我只需要获取最新的录音文件就可以上传给后台。
如果用户不手动打开系统录音文件,那怎么办呢?害~只能我们录单边的音,因为现在的Android系统,已经做了很多安全设施,包括5.1声道,所以,你只要有一个其他进程占用录音的话筒,那其他的进程就不能使用(除非你是系统的软件,要成为系统软件那你得定制手机,跟设备厂商合作了)。所以我们只能录自己说话的。自己的怎么录呢?主要是开一个service,然后监听通话:
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
tm.listen(new MyRecordListener(), PhoneStateListener.LISTEN_CALL_STATE);//监听电话状态
class MyRecordListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String phoneNumber) {
Log.d("TAG1", "空闲状态" + phoneNumber);
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
Log.d("TAG1", "空闲");
if (isRecording && mediaRecordingUtils != null) {
mediaRecordingUtils.stopRecord();
}
isRecording = false;
break;
case TelephonyManager.CALL_STATE_RINGING:
Log.d("TAG1", "来电响铃");
// 进行初始化
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
Log.d("TAG1", "摘机" + (!phoneNumber.equals("") ? phoneNumber : ""));
File directory_doc = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
//判断当前是否有这个文件夹。
FileUtils.newDirectory(directory_doc.getPath(), "dudu");
String voiceName = phoneNumber+System.currentTimeMillis() + "";
// 开始录音
mediaRecordingUtils = new MediaRecordingUtils(directory_doc.getPath() + "/dudu/" + voiceName + ".mp3");
Paper.book().write("voicePath", directory_doc.getPath() + "/dudu/" + voiceName + ".mp3");
mediaRecordingUtils.setOnAudioStatusUpdateListener(new MediaRecordingUtils.OnAudioStatusUpdateListener() {
@Override
public void onUpdate(double db, long time) {
Log.e("TAG", "initView: ================================>>>>>分贝=" + db + "=时长=" + time);
}
@Override
public void onStop(String filePath) {
Log.e("TAG", "initView: ================================>>>>>路径=" + filePath);
}
});
mediaRecordingUtils.startRecord();
isRecording = true;
default:
break;
}
super.onCallStateChanged(state, phoneNumber);
}
}
录音的功能,我用的MediaRecorder,因为这个输出格式ACC的文件,你只需要输出文件后缀改成.mp3,它就是mp3的音乐,可以直接播放,当然,你有其他需求可以格式,你可以输出其他的。
public class MediaRecordingUtils {
//文件路径
private String filePath;
private MediaRecorder mMediaRecorder;
private final String TAG = "fan";
public static final int MAX_LENGTH = 1000 * 60 * 200;// 最大录音时长,单位毫秒,1000*60*10;
private OnAudioStatusUpdateListener audioStatusUpdateListener;
/**
* 文件存储默认sdcard/record
*/
public MediaRecordingUtils() {
}
public MediaRecordingUtils(String filePath) {
this.filePath=filePath;
// File path = new File(filePath);
// if (!path.exists())
// path.mkdirs();
// this.FolderPath = filePath;
}
private long startTime;
private long endTime;
/**
* 开始录音 使用aac格式
* 录音文件
*
* @return
*/
public void startRecord() {
// 开始录音
/* ①Initial:实例化MediaRecorder对象 */
if (mMediaRecorder == null)
mMediaRecorder = new MediaRecorder();
try {
/* ②setAudioSource/setVedioSource */
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风
/* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
/*
* ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
* ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
*/
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
/* ③准备 */
mMediaRecorder.setOutputFile(filePath);
mMediaRecorder.setMaxDuration(MAX_LENGTH);
mMediaRecorder.prepare();
/* ④开始 */
mMediaRecorder.start();
// AudioRecord audioRecord.
/* 获取开始时间* */
startTime = System.currentTimeMillis();
updateMicStatus();
Log.e("TAG", "startRecord: ======= startTime" + startTime );
} catch (IllegalStateException e) {
Log.e("TAG", "startRecord: ======= call startAmr(File mRecAudioFile) failed!" + e.getMessage());
} catch (IOException e) {
Log.e("TAG", "startRecord: ======= call startAmr(File mRecAudioFile) failed!" + e.getMessage() );
}
}
/**
* 停止录音
*/
public long stopRecord() {
if (mMediaRecorder == null)
return 0L;
endTime = System.currentTimeMillis();
//有一些网友反应在5.0以上在调用stop的时候会报错,翻阅了一下谷歌文档发现上面确实写的有可能会报错的情况,捕获异常清理一下就行了,感谢大家反馈!
try {
mMediaRecorder.stop();
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
audioStatusUpdateListener.onStop(filePath);
filePath = "";
} catch (RuntimeException e) {
try {
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
File file = new File(filePath);
if (file.exists())
file.delete();
filePath = "";
} catch (Exception e1) {
}
}
return endTime - startTime;
}
/**
* 取消录音
*/
public void cancelRecord() {
try {
mMediaRecorder.stop();
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
} catch (RuntimeException e) {
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
}
File file = new File(filePath);
if (file.exists())
file.delete();
filePath = "";
}
private final Handler mHandler = new Handler();
private Runnable mUpdateMicStatusTimer = new Runnable() {
public void run() {
updateMicStatus();
}
};
private int BASE = 1;
private int SPACE = 100;// 间隔取样时间
public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
this.audioStatusUpdateListener = audioStatusUpdateListener;
}
/**
* 更新麦克状态
*/
private void updateMicStatus() {
if (mMediaRecorder != null) {
double ratio = (double) mMediaRecorder.getMaxAmplitude() / BASE;
double db = 0;// 分贝
if (ratio > 1) {
db = 20 * Math.log10(ratio);
if (null != audioStatusUpdateListener) {
audioStatusUpdateListener.onUpdate(db, System.currentTimeMillis() - startTime);
}
}
mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
}
}
public String getFilePath() {
return filePath;
}
public interface OnAudioStatusUpdateListener {
/**
* 录音中...
*
* @param db 当前声音分贝
* @param time 录音时长
*/
public void onUpdate(double db, long time);
/**
* 停止录音
*
* @param filePath 保存路径
*/
public void onStop(String filePath);
}
}
这段代码使用的别人写好的,如果有侵权,请联系我删除。
然后就完成了单边录音。保存在手机Documents文件夹下dudu下。我采用的命名为手机号+当前时间戳。
然后我们在上传给后台的时候先判断当前本地有没有系统录音的文件,如果没有就采用自己录的单边的。记得,每次上传完成后要把当前存下来的录音文件删除,避免占用过大内存。最最最重要的是,每次录音的运行时权限一定别忘了,要不然程序GG。