You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OPPO设备使用VOICE_COMMUNICATION录音通话中断问题

问题分析与解决方案

核心问题原因

OPPO ColorOS系统对通话场景下的音频录制有严格的权限管控:

  • VOICE_CALL/VOICE_DOWNLINK属于系统级音频源,普通第三方应用无法直接访问,调用会触发权限崩溃
  • VOICE_COMMUNICATION在通话发起时,系统会强制抢占音频焦点并限制第三方应用的麦克风访问,导致录音中断,仅在通话结束后恢复

可行解决方案

1. 申请OPPO专属通话录音权限

部分OPPO机型在隐私设置中存在单独的通话录音权限,需引导用户手动开启:

  • 路径:设置 > 隐私 > 权限管理 > 找到你的应用 > 开启「通话录音」权限
  • 注意:该权限无需在Manifest中声明,需通过弹窗引导用户跳转设置页

2. 改用MediaProjection录制系统音频

通过MediaProjection API捕获系统音频流(包含通话双方声音),这是目前第三方APP在OPPO设备上实现通话录音的可行方案:

private const val REQUEST_CODE_MEDIA_PROJECTION = 1001

private fun startSystemAudioRecording(file: File) {
    val mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
    val intent = mediaProjectionManager.createScreenCaptureIntent()
    // 启动Activity获取权限结果(需在Activity中实现)
    (context as Activity).startActivityForResult(intent, REQUEST_CODE_MEDIA_PROJECTION)
}

// 在Activity的onActivityResult中处理权限回调
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_CODE_MEDIA_PROJECTION && resultCode == Activity.RESULT_OK) {
        val mediaProjection = (getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager)
            .getMediaProjection(resultCode, data!!)
        
        // 构建AudioRecord录制系统音频
        val sampleRate = 44100
        val channelConfig = AudioFormat.CHANNEL_IN_MONO
        val audioFormat = AudioFormat.ENCODING_PCM_16BIT
        val bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)

        val audioRecord = AudioRecord.Builder()
            .setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX)
            .setAudioFormat(AudioFormat.Builder()
                .setEncoding(audioFormat)
                .setSampleRate(sampleRate)
                .setChannelMask(channelConfig)
                .build())
            .setBufferSizeInBytes(bufferSize)
            .setMediaProjection(mediaProjection)
            .build()

        // 启动录制并写入文件
        audioRecord.startRecording()
        CoroutineScope(Dispatchers.IO).launch {
            val buffer = ByteArray(bufferSize)
            FileOutputStream(file).use { outputStream ->
                while (audioRecord.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
                    val read = audioRecord.read(buffer, 0, bufferSize)
                    if (read > 0) {
                        outputStream.write(buffer, 0, read)
                    }
                }
            }
            audioRecord.stop()
            audioRecord.release()
            mediaProjection.stop()
        }
    }
}

3. 调整音频焦点策略(效果有限,仅作补充)

注册音频焦点监听器,在通话导致焦点丢失时尝试重新申请录音:

private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
    when (focusChange) {
        AudioManager.AUDIOFOCUS_LOSS -> {
            recorder?.stop()
        }
        AudioManager.AUDIOFOCUS_GAIN -> {
            recorder?.apply {
                reset()
                setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
                setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
                setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
                setOutputFile(file.absolutePath)
                prepare()
                start()
            }
        }
    }
}

// 在start()方法中添加音频焦点申请
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val focusResult = audioManager.requestAudioFocus(
    audioFocusChangeListener,
    AudioManager.STREAM_VOICE_CALL,
    AudioManager.AUDIOFOCUS_GAIN
)
if (focusResult != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    Log.e("Recorder", "Failed to acquire audio focus")
    return
}

4. 备选方案:使用MIC音频源(仅录本地麦克风)

如果不需要录制对方声音,可以改用MediaRecorder.AudioSource.MIC,该源在通话时不会被系统完全限制:

// 修改start()中的音频源设置
val audioSource = MediaRecorder.AudioSource.MIC
mediaRecorder.apply {
    setAudioSource(audioSource)
    setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
    setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
    setOutputFile(file.absolutePath)
    prepare()
    start()
    recorder = this
}

代码优化建议

  1. 修正日志错误:当前代码中日志打印Recording started with VOICE_DOWNLINK,实际使用的是VOICE_COMMUNICATION,需统一
  2. 完善权限适配:Android 10+建议改用Scoped Storage替代WRITE_EXTERNAL_STORAGE权限
  3. 增强异常处理:在stop()方法中添加try-catch,避免空指针或状态异常

内容的提问来源于stack exchange,提问作者Ahmed Farouk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 11:30:17