通话录音功能因为涉及隐私问题,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;
}
spaudioPolicyEffects;
{
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;
}
}