通话录音自动上传功能

      需求:项目中有一期需要做通话录音自动上传功能需求。订单详情页面,用户拨打电话以后,自动将通话录音上传到云端服务器。原来的方式是通话完成,用户从本地文件夹中选择对应的录音文件,然后上传云端。因为业务人员感觉这个操作比较麻烦,因此希望能够自动上传通话录音。

    

    调研:  自动上传通话录音功能包括两个方面:

    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的小米手机,也能够实现自动抓取通话录音文件了。

      总结:对于功能开发,首先需要从安全型方面考虑,权限方面需要优先考虑。








你可能感兴趣的:(安卓开发,android)