Asterisk怎样实现通话录音

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Asterisk怎样实现通话录音相关的知识,希望对你有一定的参考价值。

参考技术A Monitor():用于记录通话过程中录音的功能。
语法:Monitor(ext,basename,flags)
ext: 用于指定改录音文件的格式,默认的将是wav文件
basename: 该录音文件的文件名
flags : 。。。。。。。。。。。。

实例一:
复制代码
exten => _x.,1,Answer()
exten => _x.,n,Wait(1)
exten => _x.,n,Monitor(wav,asterisk-help)
exten => _x.,n,Dial(SIP/$EXTEN)
exten => _x.,n,Hangup()
当呼分机后,将在 “/var/spool/asterisk/monitor”目录下生成 "asterisk-help-in.wav"和"asterisk-help-out.wav"两个文件

实例二:指定录音路径

复制代码
exten => _x.,1,Answer()
exten => _x.,n,Wait(1)
exten => _x.,n,Monitor(wav,/root/asterisk-help) ;这里还可以指定到录音到/root目录下
exten => _x.,n,Dial(SIP/$EXTEN)
exten => _x.,n,Hangup()

Android 通话录音功能

通话录音功能因为涉及隐私问题,Android 6.0上就移除官方的通话录音接口,只能通过其他方式去获取调用。

录音时需要设置音频类型,系统中定义以下几种

(MediaRecorder.AudioSource)

CAMCORDER 录音来源于同方向的相机麦克风相同,若相机无内置相机或无法识别,则使用预设的麦克风

DEFAULT 默认音频源

MIC 录音来源为主麦克风

REMOTE_SUBMIX 用于远程呈现的音频流的子混音的音频源,需要Manifest.permission.CAPTURE_AUDIO_OUTPUT权限,第三方应用无法申请

UNPROCESSED 与默认相同

VOICE_CALL 记录上行与下行音频源,需要Manifest.permission.CAPTURE_AUDIO_OUTPUT权限,第三方应用无法申请

VOICE_COMMUNICATION 麦克风音频源针对VoIP等语音通信进行了调整,可以接收到通话的双方语音

VOICE_DOWNLINK、VOICE_UPLINK 上行下行的语音,需要Manifest.permission.CAPTURE_AUDIO_OUTPUT权限,第三方应用无法申请

VOICE_PERFORMANCE 捕获音频的来源意味着要实时处理并播放以进行现场演出

VOICE_RECOGNITION 用于获取语音进行语音识别
 

但在通话时,类型会变成 VOICE_CALL 。这种状态下录音,需要申请 android.permission.CAPTURE_AUDIO_OUTPUT 权限,UID改成 “android.uid.system” ,只能是系统应用使用。

系统中可以通过修改源码,放开此限制,代码路径 frameworks\\av\\services\\audiopolicy\\service\\AudioPolicyInterfaceImpl.cpp

    // 这里判断是否有普通录制权限 
    if (!recordingAllowed(opPackageName, pid, uid)) {
        ALOGE("%s permission denied: recording not allowed for uid %d pid %d",
                __func__, uid, pid);
        return PERMISSION_DENIED;
    }

    // 是否是以下三种类型且未申请 CAPTURE_AUDIO_OUTPUT 权限
    if ((attr->source == AUDIO_SOURCE_VOICE_UPLINK ||
        attr->source == AUDIO_SOURCE_VOICE_DOWNLINK ||
        attr->source == AUDIO_SOURCE_VOICE_CALL) &&
        !captureAudioOutputAllowed(pid, uid)) {
        return PERMISSION_DENIED;
    }

    if ((attr->source == AUDIO_SOURCE_HOTWORD) && !captureHotwordAllowed(pid, uid)) {
        return BAD_VALUE;
    }

    sp<AudioPolicyEffects>audioPolicyEffects;
    {
        status_t status;
        AudioPolicyInterface::input_type_t inputType;

        Mutex::Autolock _l(mLock);
        {
            AutoCallerClear acc;
            // the audio_in_acoustics_t parameter is ignored by get_input()
            status = mAudioPolicyManager->getInputForAttr(attr, input, session, uid,
                                                         config,
                                                         flags, selectedDeviceId,
                                                         &inputType, portId);
        }
        audioPolicyEffects = mAudioPolicyEffects;

        if (status == NO_ERROR) {
            // enforce permission (if any) required for each type of input
            switch (inputType) {
            case AudioPolicyInterface::API_INPUT_LEGACY:
                break;
            case AudioPolicyInterface::API_INPUT_TELEPHONY_RX:
                // FIXME: use the same permission as for remote submix for now.
            case AudioPolicyInterface::API_INPUT_MIX_CAPTURE:
                if (!captureAudioOutputAllowed(pid, uid)) {
                    ALOGE("getInputForAttr() permission allowed: capture allowed");
                    //cczheng annotation for don't check android.Manifest.permission.CAPTURE_AUDIO_OUTPUT
                    /*ALOGE("getInputForAttr() permission denied: capture not allowed");
                    status = PERMISSION_DENIED;*/
                }
                break;
            case AudioPolicyInterface::API_INPUT_MIX_EXT_POLICY_REROUTE:
                if (!modifyAudioRoutingAllowed()) {
                    ALOGE("getInputForAttr() permission denied: modify audio routing not allowed");
                    status = PERMISSION_DENIED;
                }
                break;
            case AudioPolicyInterface::API_INPUT_INVALID:
            default:
                LOG_ALWAYS_FATAL("getInputForAttr() encountered an invalid input type %d",
                        (int)inputType);
            }
        }

权限检查代码 frameworks/av/media/utils/ServiceUtilities.cpp

   // 判断是否AUDIO服务、root应用、已申请权限 
   bool captureAudioOutputAllowed(pid_t pid, uid_t uid) {
        if (isAudioServerOrRootUid(uid)) return true;
        static const String16 sCaptureAudioOutput("android.permission.CAPTURE_AUDIO_OUTPUT");
        bool ok = PermissionCache::checkPermission(sCaptureAudioOutput, pid, uid);
        if (!ok) ALOGV("Request requires android.permission.CAPTURE_AUDIO_OUTPUT");
        return ok;
    }

// 判断是否系统服务或者root进程,申请普通录音权限
static bool checkRecordingInternal(const String16& opPackageName, pid_t pid,
        uid_t uid, bool start) {
    // Okay to not track in app ops as audio server or media server is us and if
    // device is rooted security model is considered compromised.
    // system_server loses its RECORD_AUDIO permission when a secondary
    // user is active, but it is a core system service so let it through.
    // TODO(b/141210120): UserManager.DISALLOW_RECORD_AUDIO should not affect system user 0
    if (isAudioServerOrMediaServerOrSystemServerOrRootUid(uid)) return true;

    // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
    // may open a record track on behalf of a client.  Note that pid may be a tid.
    // IMPORTANT: DON'T USE PermissionCache - RUNTIME PERMISSIONS CHANGE.
    PermissionController permissionController;
    const bool ok = permissionController.checkPermission(sAndroidPermissionRecordAudio, pid, uid);
    if (!ok) {
        ALOGE("Request requires %s", String8(sAndroidPermissionRecordAudio).c_str());
        return false;
    }

    String16 resolvedOpPackageName = resolveCallingPackage(
            permissionController, opPackageName, uid);
    if (resolvedOpPackageName.size() == 0) {
        return false;
    }

    AppOpsManager appOps;
    const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
    if (start) {
        if (appOps.startOpNoThrow(op, uid, resolvedOpPackageName, /*startIfModeDefault*/ false)
                != AppOpsManager::MODE_ALLOWED) {
            ALOGE("Request denied by app op: %d", op);
            return false;
        }
    } else {
        if (appOps.checkOp(op, uid, resolvedOpPackageName) != AppOpsManager::MODE_ALLOWED) {
            ALOGE("Request denied by app op: %d", op);
            return false;
        }
    }

    return true;
}

解决以上权限问题后,正常录音即可,否则即使录音了,文件也是无声的。录音最好采用 amr 编码格式,体积小音质还行,综合相对比较适合录音。

    private MediaRecorder mMediaRecorder;
    private boolean isRecording;

    private void initMediaRecorder() {
        mMediaRecorder = new MediaRecorder();

        // 设置音频来源 MIC == 麦克
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        // 设置默认音频输出格式 .amr 格式
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
        // 设置默认音频编码方式 .amr 编码
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        // 指定音频输出文件路径

        SimpleDateFormat simpleDateFormat = null;// HH:mm:ss
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
            Date date = new Date(System.currentTimeMillis());
            String fileName = simpleDateFormat.format(date);
            File file = new File(Environment.getExternalStorageDirectory(), fileName + ".amr");
            mMediaRecorder.setOutputFile(file);
        }
    }

    // 开始录音
    public void start() {
        new Thread() {
            @Override
            public void run() {
                super.run();

                if (mMediaRecorder == null) {
                    initMediaRecorder();
                }

                try {
                    sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (!isRecording) {
                    try {
                        isRecording = true;
                        mMediaRecorder.prepare();
                        mMediaRecorder.start();
                        //开始录制
                    } catch (IOException e) {
                        e.printStackTrace();
                        isRecording = false;
                    }
                }
            }
        }.start();
    }

    // 停止录音
    public void stop() {
        if (mMediaRecorder != null && isRecording) {
            isRecording = false;
            mMediaRecorder.stop();
            mMediaRecorder.release();
            mMediaRecorder = null;
        }
    }

 

以上是关于Asterisk怎样实现通话录音的主要内容,如果未能解决你的问题,请参考以下文章

asterisk如何配置视频通话?

asterisk里 freeiris 系统自动录音怎么改录音名?

关于voip电话的几个问题 asterisk 软件 openwrt路由器 sip协议

如何实现Asterisk 高可靠性解决方案

Asterisk:在SIP呼叫中120秒后“TLS清除关机警报读取数据”

如何提升Asterisk 执行脚本的性能