React Native实现通话录音:Headless Task录制的通话文件无声音求助
通话录音无声音的核心原因
- 首先,
react-native-audio-recorder-player空参调用startRecorder()时,默认使用的录音源是普通麦克风(AudioSource.MIC),该源仅能采集设备周围的环境声,通话过程中系统会将音频通路切换到专用通话链路,普通应用通过MIC源根本拿不到通话的双向音频,自然生成的文件是无声的。 - 其次,现有原生层配置存在权限拦截问题:给电话状态接收器加了
android.permission.BIND_DEVICE_ADMIN权限,该权限仅对设备管理员类应用开放,普通应用注册的接收器带这个属性时,系统发送的PHONE_STATE广播会直接被权限校验拦截,很多时候录音触发流程根本没有真正跑通。 - 第三,现有代码存在生命周期逻辑漏洞:
RecReceiver里的incomingCall是成员变量,安卓系统每次发送广播都会新建BroadcastReceiver实例,该变量永远是初始的false值,无法正确跟踪来电状态,容易导致录音启动/停止时机错位。 - 第四,现有HeadlessTask的超时设置太短,原代码中任务超时时间为5000ms,远短于正常通话时长,任务会在通话过程中被系统回收,导致录音中断。
具体修复步骤
1. 调整JS层录音启动参数
替换原有空参启动录音的逻辑,在启动时显式指定适配通话场景的录音配置,核心是修正录音源:
// 定义通话录音的配置项 const getCallRecordConfig = () => { const timeStamp = Date.now(); return { // 指定可写的录音文件路径,避免外部存储权限兼容问题 path: `${AudioUtils.DocumentDirectoryPath}/call_${timeStamp}.m4a`, // 关键:替换默认的MIC录音源 // 6 对应安卓原生AudioSource.VOICE_CALL,适配Android 9及以下版本 // 7 对应安卓原生AudioSource.VOICE_COMMUNICATION,适配高版本系统 audioSource: 7, sampleRate: 44100, bitRate: 128000, format: 'mpeg4', encoder: 'aac' } } // 在HeadlessTask的Rec函数中替换原有启动逻辑 const Rec = async (data) => { try { if (data.state === 'extra_state_offhook') { // 先清理可能残留的录音监听,避免重复绑定 audioRecorderPlayer.removeRecordBackListener(); // 传入正确配置启动录音 const startResult = await audioRecorderPlayer.startRecorder(getCallRecordConfig()); console.log('录音启动状态:', startResult); audioRecorderPlayer.addRecordBackListener((e) => { // 如果一直收不到非0的位置值,说明录音源被系统拦截 console.log('录音中,当前位置:', e.current_position); }) } else if (data.state === 'extra_state_idle') { // 通话结束停止录音 const stopResult = await audioRecorderPlayer.stopRecorder(); audioRecorderPlayer.removeRecordBackListener(); console.log('录音文件路径:', stopResult); } } catch (err) { console.log('录音流程错误:', err) } }
注意:如果高版本Android设备上配置
VOICE_CALL源后录音仍无声,可以切换为VOICE_COMMUNICATION源测试,该源是安卓专门为通话类应用开放的录音源,多数国产ROM不会拦截普通应用的调用。
2. 修正Android层配置错误
- 打开
AndroidManifest.xml,找到注册的RecReceiver标签,删掉android:permission="android.permission.BIND_DEVICE_ADMIN"属性,这个权限会导致系统广播无法正常传递到接收器。 - 清理重复的权限声明:Manifest里
CALL_PHONE、PROCESS_OUTGOING_CALLS、STORAGE等权限重复写了多次,删掉重复条目避免编译警告。 - 修正
RecReceiver.java的状态逻辑:不要把来电状态存在接收器的成员变量里,因为每次广播都会新建Receiver实例,成员变量的值不会跨广播保留,所有状态判断直接基于每次Intent携带的参数处理即可,不需要维护全局的incomingCall变量。 - 适配安卓8.0及以上的后台服务限制:普通应用在后台无法直接通过
startService启动普通后台服务,需要把RecService改成前台服务,用startForegroundService启动服务,同时在Service内弹出一个常驻通知,满足安卓后台服务的启动要求,否则系统会直接拦截服务启动,导致HeadlessTask根本跑不起来。
3. 调整HeadlessTask生命周期配置
修改RecService.java中创建HeadlessJsTaskConfig的逻辑,把原来的5000ms超时时间调整为3600000ms(1小时),避免任务因为超时被系统回收,导致录音中断。同时在Service启动后手动持有足够时长的唤醒锁,避免通话过程中系统进入休眠模式打断录音通路。
常见问题排查要点
- 录音启动后如果回调的
current_position一直是0,说明录音源被系统拦截,没有实际采集到音频,这时候需要检查系统权限里的录音权限是否已授予应用,是否被厂商的应用后台管理策略限制了录音能力。 - 部分定制ROM会强制拦截所有第三方应用的通话录音能力,这种情况没有办法通过代码绕过,需要引导用户在系统设置里给应用开启“通话录音”的特殊权限。
内容的提问来源于stack exchange,提问作者SIDDHANT JOHARI
相关产品推荐
相关产品推荐

