本文转自我的博客 android 录像提示音问题
选中拍摄时关闭声音,摄像时会有声音,设备:小米note,红米note,oppo
据查该问题属于一种防偷拍的潜规则类型设置,与android系统提供商相关,拍照/摄像的提示音所有实现都在底层实现,上层能够控制的有限。
为什么拍照可以实现静默?
静默拍照目前可以通过两种方式设置:
- android4.2以上的版本可以通过Camera提供的enableShutterSound(boolean enabled)方法禁止拍照提示音;
- 通过调整接口使用来规避提示音,设置shutter callback为null来实现,该方法在官方api中有提及;
这两种方法在底层的实现原理如下:
以android4.2的源码 为例,最终的提示音调用代码目录在:/frameworks/av/services/camera/libcameraservice/CameraClient.cpp
// snapshot taken callback
void CameraClient::handleShutter(void) {
//mPlayShutterSound 该值从上层设置 即第一种方法提供的接口实现,4.2以上版本有效
if (mPlayShutterSound) {
//声音调用
mCameraService- >playSound(CameraService::SOUND_SHUTTER);
}
sp<ICameraClient> c = mCameraClient;
if (c != 0) {
mLock.unlock();
c->notifyCallback(CAMERA_MSG_SHUTTER, 0, 0);
if (!lockIfMessageWanted(CAMERA_MSG_SHUTTER)) return;
}
disableMsgType(CAMERA_MSG_SHUTTER);
mLock.unlock();
}
留意一下handleShutter的调用情况,可以发现在CameraClient中仅有一处调用了该函数:
void CameraClient::notifyCallback(int32_t msgType, int32_t ext1,
int32_t ext2, void* user) {
LOG2("notifyCallback(%d)", msgType);
Mutex* lock = getClientLockFromCookie(user);
if (lock == NULL) return;
Mutex::Autolock alock(*lock);
CameraClient* client =
static_cast<CameraClient*>(getClientFromCookie(user));
if (client == NULL) return;
if (!client->lockIfMessageWanted(msgType)) return;
switch (msgType) {
case CAMERA_MSG_SHUTTER:
// 控制发声
// ext1 is the dimension of the yuv picture.
client->handleShutter();
break;
default:
client->handleGenericNotify(msgType, ext1, ext2);
break;
}
}
那么第二种规避发声的方法也有了合理的解释,底层的快门声调用放在了notifyCallback中,那么躲开了该回调的使用也就避开了快门的声音。
那么摄像的提示音呢?
摄像功能在java层是通过 MediaRecorder 类实现的,但是在java层包括它对应的jni层大多也只是包裹一个接口、注册等作用,到底层最后的实现还是在CameraClient类中,与提示音有关的代码如下:
status_t CameraClient::startRecordingMode() {
LOG1("startRecordingMode");
status_t result = NO_ERROR;
// if recording has been enabled, nothing needs to be done
if (mHardware->recordingEnabled()) {
return NO_ERROR;
}
// if preview has not been started, start preview first
if (!mHardware->previewEnabled()) {
result = startPreviewMode();
if (result != NO_ERROR) {
return result;
}
}
// start recording mode
enableMsgType(CAMERA_MSG_VIDEO_FRAME);
//提示音代码
mCameraService->playSound(CameraService::SOUND_RECORDING);
result = mHardware->startRecording();
if (result != NO_ERROR) {
ALOGE("mHardware->startRecording() failed with status %d", result);
}
return result;
}
关联代码上下文可以发现startRecordingMode本函数无法规避,想要通过该类实现录像功能这是必走的流程,所以相对而言正规取巧的方法在上层理论上无法操控。能够看出的是4.2中也没有预留标志位给上层去控制。
一些野路子方法如下:
- 方案一:拍摄开始时设置系统音频流静默并调整音量为0,结束时恢复;
- 方案二:找到提示音文件,通过改名/移动等方法让发音失灵,由于音频文件在系统中,所以需要root权限,该方案对用户要求比较高,不考虑;
经过测试,方案一能够搞定一部分机型的情况,但不是所有的,如中兴、小米的机器都是无效的,在一篇博客《Android Camera快门音静音问题》中找到了一种解释:
但是需要注意的是:很多机器是强制快门音的,也就是说你在app里调用上述接口也许根本不起作用,你明明enableShutterSound(false)了,但是拍照的时候快门音照样响起,原因在于烧制的系统版本里面有一个值被写死了:ro.camera.sound.forced = 1
在CameraClient代码中找一下这个系统属性值可以找到:
// enable shutter sound
status_t CameraClient::enableShutterSound(bool enable) {
LOG1("enableShutterSound (pid %d)", getCallingPid());
status_t result = checkPidAndHardware();
if (result != NO_ERROR) return result;
if (enable) {
mPlayShutterSound = true;
return OK;
}
// Disabling shutter sound may not be allowed. In that case only
// allow the mediaserver process to disable the sound.
char value[PROPERTY_VALUE_MAX];
//取到系统值来控制 mPlayShutterSound 设置权限
property_get("ro.camera.sound.forced", value, "0");
if (strcmp(value, "0") != 0) {
// Disabling shutter sound is not allowed. Deny if the current
// process is not mediaserver.
if (getCallingPid() != getpid()) {
ALOGE("Failed to disable shutter sound. Permission denied (pid %d)", getCallingPid());
return PERMISSION_DENIED;
}
}
mPlayShutterSound = false;
return OK;
}
那么很明显,在CameraClient中这个系统值的作用就是限制 mPlayShutterSound 的设置了,也是对引文那一段的解释。我们在源码的全局范围内搜索该值,可以发现除了 mPlayShutterSound 该值相关的几个cpp类中有相关的使用以外,audio_policy_hal.cpp 在该类中也可以找到对forced值的使用,经过查询可以了解到这部分是实现了提示音无视静音的功能,原理大概是在播放提示音的时候会将音量调到最大,详细的过程讲解博客见《Android 4.1 与 4.2 (老JB和小JB) 在"强制拍照摄像音"处理上的区别分析》。也就是说部分设置了ro.camera.sound.forced 的系统,采用强制静音或者调音量的方法行不通,该问题基本无解。
从测试的情况看,ZTE Q801L是可以通过命令获取到ro.camera.sound.forced为1的,实现上确实无法静默拍摄;而小米则比较有意思,首先它无法获取到forced值,但是从表现上看小米原生的相机是静默的、第三方的相机则静默不了,可以推测是MiUI内部有相关的内部接口或者公用属性去设置来控制。
结论
对于无root的应用层而言,使用系统提供的MediaRecorder实现的,针对系统的不同有以下几种情况:
- 系统强制发音,设置了ro.camera.sound.forced的,无解;-- 如中兴ZTE Q801L
- 定制化系统提供了内部接口的并且外在表现为强制发音的,暂时无解。或许可以有针对性的的对相关的定制系统查找相关的方法;--如小米note、红米note
- 系统未强制设定的,一部分无需设置(如htc、三星);一部分可以通过提供静音策略来限制,这部分要保证不引发其他的音频类型问题;(魅族?)
除此之外,自定义编写视频录制实现也是个方案,不过工作量需要评估。