需求:项目中有一期需要做通话录音自动上传功能需求。订单详情页面,用户拨打电话以后,自动将通话录音上传到云端服务器。原来的方式是通话完成,用户从本地文件夹中选择对应的录音文件,然后上传云端。因为业务人员感觉这个操作比较麻烦,因此希望能够自动上传通话录音。
调研: 自动上传通话录音功能包括两个方面:
1)通话录音的采集:用户拨打电话时,采集通话录音。这需要监听用户拨打订单电话的状态,包括电话的接通和挂断;
2)通话录音自动上传:通话结束后,将采集到的订单通话录音文件上传服务器。
这通话录音的采集有两种方案:
1)应用自身采集,这样能够无差错的建立起订单和通话录音文件的映射关系。
2)系统采集,然后抓取对应的通话录音文件。因为录音文件的生成是有系统负责,应用本身并不能进行控制。 这有一个问题:不同的系统,通话录音的保存路径不一样,录音文件的命名规则也不相同,不能够针对所有类型的设备进行处理,可以对一些特殊的设备进行处理。当然还存在安全性问题,不能准确无误的建立起订单和录音文件的映射关系。
实现:
因为方案一是一个通用方案,因此采用方案一方式实现。
1)监听用户的通话
/ * 启动通话监听 */ private void startCallListener() { if (isRooted || isMIUI) { //获得电话管理器 manager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); //为管理器设置监听器,监听电话的呼叫状态 phoneListener = new MyPhoneListener(); manager.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE); } }
/ * 电话监听器 * 说明: 监听双向通话 */ private class MyPhoneListener extends PhoneStateListener { public void onCallStateChanged(int state, String incomingNumber) { if(TextUtils.isEmpty(incomingNumber)) { return; } switch (state) { case TelephonyManager.CALL_STATE_RINGING: //来电振动 LogUtils.d(TAG, "CALL_STATE_RINGING:" + incomingNumber); break; case TelephonyManager.CALL_STATE_OFFHOOK: LogUtils.d(TAG, "CALL_STATE_OFFHOOK:" + incomingNumber); // 当接通电话开始通话时 可以进行录音 if (null != callRecordEvent && Utils.GetStringNoNil(callRecordEvent.phone).equals(incomingNumber)) { //检查是否需要进行通话录音(只有订单通话才记录通话录音) recordStartTime = System.currentTimeMillis(); phoneNumber = incomingNumber.replace(" ", ""); LogUtils.d(TAG, "onCallStateChanged recordStartTime:" + TimeUtil.format(TimeUtil.FORMAT_YYYY_MM_DD_HH_MM_SS, recordStartTime) + " phoneNumber: " + phoneNumber); handler.sendEmptyMessage(MSG_START_CALL_RECORD); } break; case TelephonyManager.CALL_STATE_IDLE: LogUtils.d(TAG, "CALL_STATE_IDLE:" + incomingNumber); //挂断电话时停止录音 handler.sendEmptyMessage(MSG_STOP_CALL_RECORD); if (null != callRecordEvent && !TextUtils.isEmpty(incomingNumber)) { recordStopTime = System.currentTimeMillis(); //录音文件事件复位等待下一次操作 callRecordEvent.phone = ""; //记录通话结束的手机号 phoneNumber = incomingNumber.replace(" ", ""); LogUtils.d(TAG, "onCallStateChanged recordStopTime:" + TimeUtil.format(TimeUtil.FORMAT_YYYY_MM_DD_HH_MM_SS, recordStopTime) + " phoneNumber: " + phoneNumber); } break; } } }
2)监听到用户启动通话接通时,开始启动通话录音:
/ * 启动通话录音 * 说明: 只有Root设备才能启动通话录音功能 */ private void startCallRecord() { if (isRooted && !startFlag) { // 创建录音器 createMediaRecorder(); // 开始记录录音 startRecording(); } }
创建录音器:
/ * 创建录音器对象 */ private void createMediaRecorder() { //1) Create MediaRecorder mRecorder = new MediaRecorder(); // Set audio and video source and encoder try { //1代表单声道,2代表双声道(立体声) mRecorder.setAudioChannels(2); //2) 这两项需要放在setOutputFormat之前 mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL); } catch (Exception e) { e.printStackTrace(); } try { //3) Set output file format(mp4格式) mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); } catch (Exception e) { e.printStackTrace(); } try { //4) 这两项需要放在setOutputFormat之后 mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); } catch (Exception e) { e.printStackTrace(); } // Set output file path //初始化缓存目录 createOutputFile(); try { //设置输出文件路径 mRecorder.setOutputFile(audioPath); } catch (Exception e) { e.printStackTrace(); } }
开始通话录音
/ * 开始录音 */ private void startRecording() { LogUtils.d(TAG, "bf mRecorder.prepare()"); try { mRecorder.prepare(); } catch (Exception e) { e.printStackTrace(); } LogUtils.d(TAG, "af mRecorder.prepare()"); try { LogUtils.d(TAG, "bf mRecorder.start()"); mRecorder.start(); // Recording is now started } catch (Exception e) { e.printStackTrace(); //启动出错 if(!isMIUI){ //MIUI支持录音文件抓取 ToastUtils.showToast(this, R.string.audio_start_failed, Toast.LENGTH_SHORT); } handler.sendEmptyMessage(MSG_START_CALL_RECORD_FAILED); return; } //设置标签: 已经启动通话录音 startFlag = true; LogUtils.d(TAG, "af mRecorder.start()"); LogUtils.d(TAG, "Start recording ..."); }
3)通话结束时停止通话录音开始上传
/ * 结束通话录音 * 说明: 如果自动录音成功优先使用自动录音; * 如果自动录音启动失败,且是小米系统,可以尝试抓取录音文件进行上传 * (1、有些手机未Root权限判断为已经Root;2、系统自身已经开启通话录音导致自动录音失败)。 */ private void stopCallRecord() { if (isRooted && startFlag) { //如果是Root设备查找自我录音的文件 // (通话录音开启成功时使用自动录音的文件;否则使用小米手机的通话录音) //停止录音设备 stopMediaRecorder(); //录音文件是非空的录音文件 if (FileHelper.getFileSize(audioPath) > 0) { //录音结束开始启动通话录音文件上传 handler.sendEmptyMessage(MSG_UPLOAD_CALL_RECORD); } else { //空文件删除 FileHelper.deleteFile(audioPath); } // Set button status flag startFlag = false; } else if (isMIUI) { //MIUI系统设备有自动通话录音功能抓取对应的通话录音文件 if(null != callRecordEvent) { //只有在贷后自动录音处理才抓取录音文件 startFilterAudioFile(phoneNumber, recordStartTime, callRecordEvent.son_order_id); } } }
产生问题:程序安装后,发现有的手机运行没有问题,有的手机运行录音一直不成功,录音文件大小为0。是什么原因导致录音不成功呢?
通话录音是一种比较危险权限,系统未Root时,除了系统自身授权,其他的应用是无法获取到这种权限的。
因此即使看到应用已经授权录音权限,如果手机未Root,自己采集通话录音不能成功。红米Note4是稳定版的系统,
因此应用不能采集到通话录音。
出现这种情况,有两种解决方案:
方式一:将手机Root。这是一种比较危险的操作。
方式二:应用不进行录音,有系统录音,应用抓取系统的录音文件进行上传。
对于未Root的手机,采用方式二进行处理。工作人员使用的手机是红米手机。小米手机的通话录音文件,
保存在一个固定的目录:
内存目录/MIUI/sound_recorder/call_rec,可以从目录中抓取通话录音文件。
小米手机通话录音文件的保存的文件名也很有特征:通话录音@手机号xxx_时间串.mp3,
我们可以根据手机号+时间串来抓取对应的通话录音文件。
/ * 从小米文件夹中抓取对应的录音文件 * * @param phoneNumber : 手机号 * @param recordStartTime : 录音文件开始时间 * @param son_order_id : 订单ID */ private void startFilterAudioFile(final String phoneNumber, final long recordStartTime, String son_order_id) { if (TextUtils.isEmpty(phoneNumber) || TextUtils.isEmpty(son_order_id)) { //手机号或者订单号不存在不需要上传 return; } //首先检查录音文件保存目录是否存在 if (!FileHelper.fileIsExists(Config.callRecordDir)) { //对应的录音文件夹不存在 return; } //抓取对应的录音文件(手机号+通话开始时间) File dir = new File(Config.callRecordDir); if (!dir.isDirectory()) { return; } //获取其中的子文件列表 File[] files = dir.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { if (pathname.isDirectory()) { //子文件夹不处理 return false; } //检测录音文件有效性 if (pathname.length() <= 0) { //删除空白文件 FileHelper.deleteFile(pathname.getPath()); return false; } //过滤文件名日志 String simpleName = pathname.getName(); //去掉所有的空格字符 simpleName = simpleName.replace(" ", ""); if (!simpleName.contains(phoneNumber)) { return false; } //过滤文件名中的时间 String dateStr = parseDate(simpleName); if(TextUtils.isEmpty(dateStr)) { return false; } //最早的时间 Date fileDate = DateUtils.strToDate(dateStr, TimeUtil.FORMAT_NO_SPLIT_YYYY_MM_DD_CHS_HH_MM_SS); //录音文件时间一定在录音开始时间和录音结束时间的监听范围内 long fileTime = fileDate.getTime(); return fileTime >= 0 && fileTime >= recordStartTime && fileTime <= recordStopTime; } }); if (null == files || files.length <= 0) { //未找到符合条件的文件 return; } //有多个文件符合条件时优先查找文件名刚好一致的文件 File destFile = null; if(files.length > 1) { //如果有多个文件符合条件优先获取时间刚好相同的文件 String str = TimeUtil.format(TimeUtil.FORMAT_NO_SPLIT_YYYY_MM_DD_CHS_HH_MM_SS, recordStartTime); for (File file : files) { if(file.getName().contains(str)) { //找到一个完全匹配的文件 destFile = file; break; } } } //如果没有找到完全匹配的文件默认采用过滤文件列表中第一个 if(null == destFile) { destFile = files[0]; } //开始上传录音文件 startUploadFile(destFile.getPath(), son_order_id); }
这样,对于未Root的小米手机,也能够实现自动抓取通话录音文件了。
总结:对于功能开发,首先需要从安全型方面考虑,权限方面需要优先考虑。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/98686.html