Android 8.1/9.0 MTK Camera源码分析之快门声音控制
在Android 8.1上mtk camera有控制快门声音的接口,但是并没有了控制录像快门声音的接口。之所以会有这个现象,主要原因是mtk camera仍旧使用的camera api1的接口。不同于camera api2,快门声音直接在上层控制,减少了很多麻烦。这一点在mtk 9.0的camera代码中就可以体现。
针对camera api1
快门声音控制
/frameworks/base/core/java/android/hardware/Camera.java中有定义控制快门声音的api。
ap中直接调用这个api来控制就可以快门声音,不过该api是camera api1,所以使用范围有限制。
public final boolean enableShutterSound(boolean enabled) {
if (!enabled) {
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
IAudioService audioService = IAudioService.Stub.asInterface(b);
try {
if (audioService.isCameraSoundForced()) return false;
} catch (RemoteException e) {
Log.e(TAG, "Audio service is unavailable for queries");
}
}
return _enableShutterSound(enabled);
}
所以在ap层调用,直接使用parameter对象调用。
mCameraProxy.enableShutterSound(isShutterSoundOn); //控制拍照声音
api提供了enableShutterSound,也提供了disableShutterSound来直接关闭快门音:
public final boolean disableShutterSound() {
return _enableShutterSound(/*enabled*/false);
}
_enableShutterSound这个函数是调jni:
private native final boolean _enableShutterSound(boolean enabled);
在android_hardware_camera.cpp中定义:
static jboolean android_hardware_Camera_enableShutterSound(JNIEnv *env, jobject thiz,
jboolean enabled)
{
ALOGV("enableShutterSound");
//get_native_camera指向的是Camera.h/Camera.cpp
sp<Camera> camera = get_native_camera(env, thiz, NULL);
if (camera == 0) return JNI_FALSE;
int32_t value = (enabled == JNI_TRUE) ? 1 : 0;
//往client端发送指令CAMERA_CMD_ENABLE_SHUTTER_SOUND
status_t rc = camera->sendCommand(CAMERA_CMD_ENABLE_SHUTTER_SOUND, value, 0);
if (rc == NO_ERROR) {
return JNI_TRUE;
} else if (rc == PERMISSION_DENIED) {
return JNI_FALSE;
} else {
jniThrowRuntimeException(env, "enable shutter sound failed");
return JNI_FALSE;
}
}
指令CAMERA_CMD_ENABLE_SHUTTER_SOUND最终会被传递到CameraClient.cpp。
文件路径:frameworks/av/services/camera/libcameraservice/api1/CameraClient.cpp。
status_t CameraClient::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) {
LOG1("sendCommand (pid %d)", getCallingPid());
int orientation;
Mutex::Autolock lock(mLock);
status_t result = checkPidAndHardware();
if (result != NO_ERROR) return result;
if (cmd == CAMERA_CMD_SET_DISPLAY_ORIENTATION) {
..........
} else if (cmd == CAMERA_CMD_ENABLE_SHUTTER_SOUND) { //快门声音控制
switch (arg1) {
case 0:
return enableShutterSound(false);//实际控制函数
case 1:
return enableShutterSound(true);//实际控制函数
default:
return BAD_VALUE;
}
return OK;
} else if (cmd == CAMERA_CMD_PLAY_RECORDING_SOUND) {
sCameraService->playSound(CameraService::SOUND_RECORDING_START);
} else if (cmd == CAMERA_CMD_SET_VIDEO_BUFFER_COUNT) {
// Silently ignore this command
return INVALID_OPERATION;
} else if (cmd == CAMERA_CMD_PING) {
// If mHardware is 0, checkPidAndHardware will return error.
return OK;
}
//将指令继续往hal层,用来通知底层,当快门按钮被按下时,及时响应音频播放。
return mHardware->sendCommand(cmd, arg1, arg2);
}
然后再看这个enableShutterSound(true)实际控制快门音的函数:
// 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;
}
// the camera2 api legacy mode can unconditionally disable the shutter sound
if (mLegacyMode) {
ALOGV("%s: Disable shutter sound in legacy mode", __FUNCTION__);
mPlayShutterSound = false;
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];
////设置了一个属性ro.camera.sound.forced,来通知
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;
}
看到这,有个想法,不知道在ap端直接设置系统属性property_get(“ro.camera.sound.forced”, value, “0”);能否达到控制快门音效果。后面在尝试一下看看。不过如果使用的api2的就不用上面这么麻烦了。
到这里其实已经看到,通过上层api调用,到service端接收指令,通知音频打开开关。那么触发时机还没有看到,不过正常逻辑一定是在按下快门之后,照片数据已经通过JpegCallback回调到上层时才开始播放。所以可以看下service中takePicture函数的处理。
// take a picture - image is returned in callback
status_t CameraClient::takePicture(int msgType) {
LOG1("takePicture (pid %d): 0x%x", getCallingPid(), msgType);
Mutex::Autolock lock(mLock);
status_t result = checkPidAndHardware();
if (result != NO_ERROR) return result;
if ((msgType & CAMERA_MSG_RAW_IMAGE) &&
(msgType & CAMERA_MSG_RAW_IMAGE_NOTIFY)) {
ALOGE("CAMERA_MSG_RAW_IMAGE and CAMERA_MSG_RAW_IMAGE_NOTIFY"
" cannot be both enabled");
return BAD_VALUE;
}
// We only accept picture related message types
// and ignore other types of messages for takePicture().
//发送相关msg,其中就包括了shutter信息CAMERA_MSG_SHUTTER
int picMsgType = msgType
& (CAMERA_MSG_SHUTTER |
CAMERA_MSG_POSTVIEW_FRAME |
CAMERA_MSG_RAW_IMAGE |
CAMERA_MSG_RAW_IMAGE_NOTIFY |
CAMERA_MSG_COMPRESSED_IMAGE);
enableMsgType(picMsgType); //往hal层继续传递
//调用hal层takePicture函数
return mHardware->takePicture();
}
而service把相关msg回调消息传递给hal层,也注册了消息回调机制,用来接收底层的回调。
可以看到在init中就注册了像hardware注册了回调接口。
status_t CameraClient::initialize(sp<CameraProviderManager> manager) {
......
char camera_device_name[10];
snprintf(camera_device_name, sizeof(camera_device_name), "%d", mCameraId);
mHardware = new CameraHardwareInterface(camera_device_name);
res = mHardware->initialize(manager);
if (res != OK) {
ALOGE("%s: Camera %d: unable to initialize device: %s (%d)",
__FUNCTION__, mCameraId, strerror(-res), res);
mHardware.clear();
return res;
}
//////接口注册回调,其中notifyCallback就是消息通知的回调
mHardware->setCallbacks(notifyCallback,
dataCallback,
dataCallbackTimestamp,
handleCallbackTimestampBatch,
(void *)(uintptr_t)mCameraId);
......
return OK;
}
接下来看下notifyCallback函数接收msg的处理。shutter的信息是CAMERA_MSG_SHUTTER。
void CameraClient::notifyCallback(int32_t msgType, int32_t ext1,
int32_t ext2, void* user) {
LOG2("notifyCallback(%d)", msgType);
sp<CameraClient> client = getClientFromCookie(user);
if (client.get() == nullptr) return;
if (!client->lockIfMessageWanted(msgType)) return;
switch (msgType) {
//!++
case MTK_CAMERA_MSG_EXT_NOTIFY:
client->handleMtkExtNotify(ext1, ext2); // Callback extended msg notification.
break;
//!--
case CAMERA_MSG_SHUTTER:
// ext1 is the dimension of the yuv picture.
client->handleShutter(); //调用handleShutter处理拍照快门声音
break;
default:
client->handleGenericNotify(msgType, ext1, ext2);
break;
}
}
再看client->handleShutter()函数:
// snapshot taken callback
void CameraClient::handleShutter(void) {
if (mPlayShutterSound) { //直接就指向到CameraService playSound
sCameraService->playSound(CameraService::SOUND_SHUTTER);
}
sp<hardware::ICameraClient> c = mRemoteCallback;
if (c != 0) {
mLock.unlock();
c->notifyCallback(CAMERA_MSG_SHUTTER, 0, 0);
if (!lockIfMessageWanted(CAMERA_MSG_SHUTTER)) return;
}
disableMsgType(CAMERA_MSG_SHUTTER);
// Shutters only happen in response to takePicture, so mark device as
// idle now, until preview is restarted
sCameraService->updateProxyDeviceState(
hardware::ICameraServiceProxy::CAMERA_STATE_IDLE,
mCameraIdStr, mCameraFacing, mClientPackageName);
mLock.unlock();
}
在cameraservers.cpp中分别有:加载声音、资源播放声音等等方法,快门音就是playSound函数。
加载声音,资源路径的引用。
void CameraService::loadSound() {
ATRACE_CALL();
Mutex::Autolock lock(mSoundLock);
LOG1("CameraService::loadSound ref=%d", mSoundRef);
if (mSoundRef++) return;
//!++
#if 0
mSoundPlayer[SOUND_SHUTTER] = newMediaPlayer("/system/media/audio/ui/camera_click.ogg");
mSoundPlayer[SOUND_RECORDING_START] = newMediaPlayer("/system/media/audio/ui/VideoRecord.ogg");
mSoundPlayer[SOUND_RECORDING_STOP] = newMediaPlayer("/system/media/audio/ui/VideoStop.ogg");
#else
if( pthread_create(&mloadSoundTThreadHandle, NULL, loadSoundThread, this) != 0 )
{
ALOGE("loadSound pthread create failed");
}
#endif
//!--
}
//!++
void CameraService::loadSoundImp() {
LOG1("[CameraService::loadSoundImp] E");
mSoundPlayer[SOUND_SHUTTER] = newMediaPlayer("/system/media/audio/ui/camera_click.ogg");
mSoundPlayer[SOUND_RECORDING_START] = newMediaPlayer("/system/media/audio/ui/VideoRecord.ogg");
mSoundPlayer[SOUND_RECORDING_STOP] = newMediaPlayer("/system/media/audio/ui/VideoStop.ogg");
LOG1("[CameraService::loadSoundImp] X");
}
再看声音播放playSound函数,需要先等待waitloadSoundDone()加载完毕,在进行资源播放。播放方式是使用的MediaPlayer。
void CameraService::playSound(sound_kind kind) {
ATRACE_CALL();
LOG1("playSound(%d)", kind);
Mutex::Autolock lock(mSoundLock);
//!++
waitloadSoundDone();
//!--
sp<MediaPlayer> player = mSoundPlayer[kind];
if (player != 0) {
player->seekTo(0);
player->start();
}
}
针对camera api2
快门声音控制
mtk9.0 底层使用hal3.0,上层使用camera api2,所以针对8.1的上添加拍照快门声音的控制不同了。已经用不到了。
而在mtk9.0,camera2 app中,控制拍照声音、录像快门声音都是在app中来进行设置的。这样更加方便了。
拍照通过soundPlay的方式来进行控制:mICameraContext.getSoundPlayback().play(ISoundPlayback.SHUTTER_CLICK);
在注册了captureSession后,回调函数onCaptureStarted中表明按下快门,capture捕捉数据开始,在此响应拍照声音即可。
@Override
public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long
timestamp, long frameNumber) {
super.onCaptureStarted(session, request, timestamp, frameNumber);
if (mCamera2Proxy == null || session.getDevice() != mCamera2Proxy.getCameraDevice()) {
return;
}
if (CameraUtil.isStillCaptureTemplate(request)) {
LogHelper.d(TAG, "[onCaptureStarted] capture started, frame: " + frameNumber);
if(isShutterSoundOn() ) {
mICameraContext.getSoundPlayback().play(ISoundPlayback.SHUTTER_CLICK);
}
}
}
api1的拍照快门声音流程相对麻烦,而api2的拍照快门音,完全可以通过captureSession回调中的函数来进行播放,这样非常方便,流程上简洁了很多。