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 }
代码优化建议
- 修正日志错误:当前代码中日志打印
Recording started with VOICE_DOWNLINK,实际使用的是VOICE_COMMUNICATION,需统一 - 完善权限适配:Android 10+建议改用Scoped Storage替代
WRITE_EXTERNAL_STORAGE权限 - 增强异常处理:在
stop()方法中添加try-catch,避免空指针或状态异常
内容的提问来源于stack exchange,提问作者Ahmed Farouk
相关产品推荐
相关产品推荐

