android 录像提示音问题

410 查看

本文转自我的博客 android 录像提示音问题

选中拍摄时关闭声音,摄像时会有声音,设备:小米note,红米note,oppo

据查该问题属于一种防偷拍的潜规则类型设置,与android系统提供商相关,拍照/摄像的提示音所有实现都在底层实现,上层能够控制的有限。

为什么拍照可以实现静默?

静默拍照目前可以通过两种方式设置:

  1. android4.2以上的版本可以通过Camera提供的enableShutterSound(boolean enabled)方法禁止拍照提示音;
  2. 通过调整接口使用来规避提示音,设置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实现的,针对系统的不同有以下几种情况:

  1. 系统强制发音,设置了ro.camera.sound.forced的,无解;-- 如中兴ZTE Q801L
  2. 定制化系统提供了内部接口的并且外在表现为强制发音的,暂时无解。或许可以有针对性的的对相关的定制系统查找相关的方法;--如小米note、红米note
  3. 系统未强制设定的,一部分无需设置(如htc、三星);一部分可以通过提供静音策略来限制,这部分要保证不引发其他的音频类型问题;(魅族?)

除此之外,自定义编写视频录制实现也是个方案,不过工作量需要评估。