android机制系列之七 Android Camera API1架构之一 Camera API1架构

时至今日,android已经8.0+,而研究camera1的意义在于对一般的Binder机制,老的camera架构有深入的理解。目前国内,比如我的工作,至今仍然是android5.1为主, 6.0,7.0为辅,甚至还有开历史倒车的4.4的工作。更加不提近日Vivo新机升降摄像头成为了检测流氓软件的标杆了。这是因为,某app使用了camera api1,获取某些摄像头属性他必须先Camera.open()以后才能。而我推测该机器是绑定了camera.open的动作,硬件上就会升起摄像头。并解释说camera2对AR VR支持不好等等。

言归正传,对于这样的大app至今都在使用Camera1。我们还是继续研究下。

android camera架构十分的复杂,今天开始分析它,这里以android2.3.7源码为研究对象,后续可以对比下android5.1下的对于Camera1的变化。比如frameworks/av的路径是有差异。至于架构似乎差异不大。到时候再说。

(永不过时的API1)

首先建立在学习了Binder,学习了AshMemory(MemoryHeapBase)

我已经写了2篇,第一篇以java android上层开发工程师的角度;第二篇,融汇贯通C++/Java Binder.

入门学习:Binder Java入门

注意学习:Binder C++ Java

1. Anonymous Shared Memory

AShMem包含IMemory和IMemoryHeap2族,都是Binder机制的应用实例

这里写图片描述

IMemoryHeap:提供接口,访问匿名共享内存;

​ {heapFD; mBase; size;flags;device;offset}

BpMemoryHeap: 在client端的业务逻辑;

​ 里面包含一个static sp gHeapCache (内部有键值对Vectormap),保存了一个Client进程中所有的BpMemoryHeap.

MemoryHeapBase:BnMemoryHeap sever端真实的业务逻辑.

引用原文:匿名共享内存的C++访问方式:

1)服务端构造MemoryHeapBase对象时,创建匿名共享内存,并映射到服务进程的地址空间中,同时提供获取该匿名共享内存的接口函数;

2)客户端通过BpMemoryHeap对象请求服务端返回创建的匿名共享内存信息,并且将服务端创建的匿名共享内存映射到客户进程的地址空间中,在客户端也提供对应的接口函数来获取匿名共享内存的信息;

IMemory:其实是承载IMemoryHeap的一个封装对象。

​ 主要包含getMemory()返回IMemoryHeap mHeap

​ 所以与直接使用MemoryHeap相比,这里更关注的是共享内存块的起始位置offset,和大小size。

MemoryHeapBase:一般用于在进程间共享一个完整的匿名共享内存块

常用构造函数 MemoryHeapBase(size_t size, uint32_t flags = 0, char const* name = NULL);

MemoryBase: 一般用于在进程间共享一个匿名共享内存块的其中一部分。

常用构造函数MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size);

2. Camera类图

这里写图片描述

此图需要反复翻上去看,先保存对比看。

3. 建立连接流程

Camera.java

open()-> new Camera(id) -> native_setup(this, id)

JNI

//android_hardware_Camera_native_setup()
    sp<Camera> camera = Camera::connect(cameraId); //Camera:ICameraClient
    //...
    //JNICameraContext是CameraListener的子类具有notify/postData/postDataTs
    //并添加addCallbackBuffer等几个方法
    sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera);
    context->incStrong(thiz);
    camera->setListener(context);//因此,这里传入自己,可供Camera:ICameraClient回调

    // save context in opaque field
    env->SetIntField(thiz, fields.context, (int)context.get());
sp<Camera> Camera::connect(int cameraId) //Camera.cpp
{
    //注意,从这里可以判断我们在JAVA- JNI - 到这里其实都是在上层创建的那个进程里面。至今还未跨进程
    sp<Camera> c = new Camera(); //要返回的对象在这里new出来的,里面几乎没干什么
    const sp<ICameraService>& cs = getCameraService();
    //sp<IServiceManager> sm = defaultServiceManager();
    //然后sp<IBinder> binder=sm->getService(String16("media.camera"));
    //mCameraService = interface_cast<ICameraService>(binder)
    //事实上cs就是ICameraService在client端的BpCameraService(参加binder文章的附录2)
    if (cs != 0) {
        c->mCamera = cs->connect(c, cameraId);
    }
    //...
    return c;
}
sp<ICamera> CameraService::connect( //CameraService.cpp
        const sp<ICameraClient>& cameraClient, int cameraId) {
    sp<Client> client; //Client是继承的BnCamera : ICamera
    //...
    Mutex::Autolock lock(mServiceLock);
    if (mClient[cameraId] != 0) {
        //...忽略有的情况
    }
    //...//所以Hardware其实是运行在cameraService的
    sp<CameraHardwareInterface> hardware = HAL_openCameraHardware(cameraId);
    //...
    CameraInfo info;
    HAL_getCameraInfo(cameraId, &info);
    //Client就是BnCamera:ICamera,
    client = new Client(this, cameraClient, hardware, cameraId, info.facing,
                        callingPid);
    mClient[cameraId] = client;//弱引用保存
    return client; //这是运行在service端是实体类,而回到上面Camera::connect则拿到的是BpCamera
}

所以,这一步搞清楚的是,代码执行到哪里,进程在哪里,区分代码,进程。
这里写图片描述
这里的第二步,cs->connect(c, cameraId);就相当于上一篇文章提到的,这是一种回调的方式,让CameraService的内部对象client持有App进程的ICameraClient Bp对象。我们可以反复对比类图学习。ICameraClient只有3个回调。当然这些东西的用途会在第4节中继续展开解释。

所以,Camera open的流程基本如此。CameraService处包含了N(几个摄像头)*Client,Client代表着ICamera的操作对象,并保存本地wp数组中。最终JNI处有了JNICameraContext,它包含上层弱引用,又包含了Camera本地对象(它内部成员变量mCamera是远程端传回来的BpCamera:ICamera)。
(https://blog.csdn.net/jzlhll123/article/details/80865999)

总体来说,上层包括JNI持有的,我们经常接触到的Camera对象就是sp< Camera > Camera.cpp; 它的成员mCamera:ICamera, 指代了cameraService里面Client对象。而这个Client对象在CameraService里面是mClient数组弱引用之一,并有一个强引用的当前Client对象。Client里面则封装了cameraService,hardware,previewBuff等,以及我们connect进去的回调mCameraClient(ICameraClient,其实就指代sp< Camera>)

3. 主动类型:startPreview为代表的上层往下的流程

java->startPreview->JNI

//JNI
static void android_hardware_Camera_startPreview(JNIEnv *env, jobject thiz)
{
    sp<Camera> camera = get_native_camera(env, thiz, NULL); //简单而言就是从context里面取出sp<Camera>
    camera->startPreview() //本app进程,上面讲到我们这里的camera是Camera:ICameraClient,于是我们要去看的代码是Camera.cpp不要搞错了逻辑。
}
//Camera.cpp
status_t Camera::startPreview() //封装模式设计
{
    sp <ICamera> c = mCamera; //上面connect讲到mCamera是Camera内部的ICamera对象
    return c->startPreview();//所以这里start preview
}
//ICamera.cpp
    status_t startPreview()
    {
        Parcel data, reply;
        data.writeInterfaceToken(ICamera::getInterfaceDescriptor());
        remote()->transact(START_PREVIEW, data, &reply); //跨进程到BnCamera端从类图看知道BnCamera是CameraService::Client
        return reply.readInt32();
    }
//CameraService.cpp ::Client
status_t CameraService::Client::startPreview() {
    return startCameraMode(CAMERA_PREVIEW_MODE);
    //继而startPreviewMode(); mHardware->startPreview(); 走到HAL去了。
}   

流程很清晰,进程也已经清楚,略掉流程图的绘制。

这一步搞清楚的是(阅读时,要反复翻阅类图理解),JAVA JNI 的JNICameraContext, 持有本地sp\

4. 回调类型:以postData(int msgType, const sp< IMemory>& dataPtr)为代表的回调机制

4.1 第1步,看设置监听的流程

//android_hardware_Camera_native_setup()
    sp<Camera> camera = Camera::connect(cameraId); //Camera:ICameraClient
    sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera);
    context->incStrong(thiz);
    camera->setListener(context);
    //前面已经分析过camera就是app进程的东西,JNICameraContext传入到Camera,只是平常我们的监听模式而已。
    //Camera.cpp
void Camera::setListener(const sp<CameraListener>& listener)
{
    Mutex::Autolock _l(mLock);
    mListener = listener; //其实还是在app进程
}

    //Camera.cpp callback from camera service when frame or image is ready
void Camera::dataCallback(int32_t msgType, const sp<IMemory>& dataPtr)
{
    sp<CameraListener> listener;
    {
        Mutex::Autolock _l(mLock);
        listener = mListener;
    }
    if (listener != NULL) {
        listener->postData(msgType, dataPtr); //所以,我们需要关注的是dataCallback从哪里来的。这里其实是本进程。肯定有CameraService跨进程到这个方法
    }
}

这里纯粹是在一个进程,2个对象中,最普通的监听注册而已。

首先我们要明确dataCallback等3个方法,它的对象是Camera,已经传递给CameraService了。

所以在阅读代码要搞清楚,Camera.cpp Camera::dataCallback(int32_t msgType, const sp& dataPtr)其实是BnCameraClient; CameraService就会使用存下来的BpCameraClient:ICameraClient调用对应方法即可。

4.2 第2步,找发送端

查看CameraService的时候,就是类图中很多的CameraService::Client::handleXXX(),比如

void CameraService::Client::handleGenericData(int32_t msgType,
    const sp<IMemory>& dataPtr) {
    sp<ICameraClient> c = mCameraClient; //略lock
    c->dataCallback(msgType, dataPtr); //略判断0
}

而所有的handleXXX都是从static方法而来,

 static void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2, void* user);
 static void dataCallback(int32_t msgType, const sp<IMemory>& dataPtr, void* user);
 static void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr, void* user);

而它们是在new CameraService:Client()的时候,给hardware对象设置的回调。mHardware>setCallbacks(notifyCallback,dataCallback,dataCallbackTimestamp, (void *)cameraId);

HAL底下,暂时不分析。MTK的源码中可以查阅部分实现。这里略过。

这一步的分析过来,可以倒过来看。这就是一个基于Binder的远程回调方式。原理与[binder/AIDL回调机制]

20180702备注:
从上面逻辑可以看出,hardware有buff以后,首先通过静态func注册进去的3个方法回调到cameraservice,然后binder机制调用到BnCameraClient,即Camera.cpp中的mListener会被接受。
然而,由于Camera不仅仅是preview,还涉及到mediaRecorder,则会出现mListener在录制的时候,被设置到CameraSource里面去了。
因此,此处的回调也就不能去到JNIContext了。

这个会在后续展开讲解。android2.3 camera架构相对简单(虽然已经很庞大了),android5以上的camera更加复杂,更多的模型嵌套。逻辑上也有所变化。

5. takePicture 内存共享研究

这里写图片描述

整体拍照流程,虚线左边是app进程,右边是CameraService进程。基本上都是Binder流程,不再赘述。

按照第一章节的理解,我们必须找到在哪里申明MemoryHeapBase或者MemoryBase的地方。

在CameraHardwareStub.h定义

    sp<MemoryHeapBase>  mPreviewHeap; *4大小内存
    sp<MemoryHeapBase>  mRawHeap;
    sp<MemoryBase>      mBuffers[kBufferCount]; 4块内存 
                (可以感受到MemoryBase和MemoryHeapBase描述的内存起始和大小不同点)

上流程图中,因为每个平台实现不同,简要描述,CameraHardwareStub::previewThread() ,内部代码(略)可以自行分析,是从hardware去takePicture并给出了共享的mBuffs一个。如果没有拷贝需求则直接给出去。如果有拷贝的需求,则通过CameraService内部的sp<MemoryHeapBase> mPreviewBuffer运作一番。之后就开始走回调流程。

最后共享回调到,JNI copyAndPost()在这里,

void JNICameraContext::copyAndPost(JNIEnv* env, const sp<IMemory>& dataPtr, int msgType)
{  //dataPtr前面提到的共享Buff
    jbyteArray obj = NULL;

    // allocate Java byte array and copy data
    if (dataPtr != NULL) {
        ssize_t offset; size_t size;
        sp<IMemoryHeap> heap = dataPtr->getMemory(&offset, &size);//从共享内存对象中提取出来
        uint8_t *heapBase = (uint8_t*)heap->base();

        if (heapBase != NULL) {
            const jbyte* data = reinterpret_cast<const jbyte*>(heapBase + offset);//转成数组
            if (!mManualBufferMode) {
                LOGV("Allocating callback buffer");
                obj = env->NewByteArray(size);
            } else {
                // Vector access should be protected by lock in postData()
                if(!mCallbackBuffers.isEmpty()) { //本地化缓存机制
                    LOGV("Using callback buffer from queue of length %d", mCallbackBuffers.size());
                    jbyteArray globalBuffer = mCallbackBuffers.itemAt(0);
                    mCallbackBuffers.removeAt(0);

                    obj = (jbyteArray)env->NewLocalRef(globalBuffer);
                    env->DeleteGlobalRef(globalBuffer);

                    if (obj != NULL) { //做了一些内存申请大小判断 可以忽略
                        jsize bufferLength = env->GetArrayLength(obj);
                        if ((int)bufferLength < (int)size) {
                            LOGE("Manually set buffer was too small! Expected %d bytes, but got %d!",
                                 size, bufferLength);
                            env->DeleteLocalRef(obj);
                            return;
                        }
                    }
                }
               //....略
            }

            if (obj == NULL) {
                env->ExceptionClear();LOGE("Couldn't allocate byte array for JPEG data");
            } else {
                env->SetByteArrayRegion(obj, 0, size, data);//把共享内存data类型转换到obj中
            }
        } else {
            LOGE("image heap is NULL");
        }
    }

    // post image data to Java 回调到java层
    env->CallStaticVoidMethod(mCameraJClass, fields.post_event,
            mCameraJObjectWeak, msgType, 0, 0, obj);//后4对应handler message的参数
    if (obj) {
        env->DeleteLocalRef(obj);
    }
}

而fields.post_event = env->GetStaticMethodID(clazz, “postEventFromNative”,
“(Ljava/lang/Object;IIILjava/lang/Object;)V”);

private static void postEventFromNative(Object camera_ref,
                                        int what, int arg1, int arg2, Object obj)
{
    Camera c = (Camera)((WeakReference)camera_ref).get(); //为null保护略
    if (c.mEventHandler != null) {
        Message m = c.mEventHandler.obtainMessage(what, arg1, arg2, obj);//对应JNI处的msgType, 0, 0, obj对应过来,我们查出msgType=what=CAMERA_MSG_COMPRESSED_IMAGE = 0x100;上下层都对应
        c.mEventHandler.sendMessage(m); 
    }
}

//Handler里面,接受消息终于回到了上层
case CAMERA_MSG_COMPRESSED_IMAGE:
    if (mJpegCallback != null) {
        mJpegCallback.onPictureTaken((byte[])msg.obj, mCamera);
    }

因此,可以看到AShMem在takePicture流程上的使用,在日后其他类似流程的共享机制上会更加轻松地阅读和使用。

总结

  • 研究的android2.3.7的源码。后续再补充5.1下的camera1的差异。也多亏老代码的目录分明,代码更加简洁。

  • 共享内存机制,掌握2个类,他们的应用场景的差异。

    MemoryHeapBase:一般用于在进程间共享一个完整的匿名共享内存块

    MemoryBase: 一般用于在进程间共享一个匿名共享内存块的其中一部分。

    他们都是作为binder对象共享出去的;不过在使用中一般是以MemoryHeapBase在定义共享端申明,而用MemoryBase共享具体某一部分。

  • 对JNI和cameraService持有的对象,他们的关系,Bp,Bn,接口如何流转目前已经十分清晰;

  • 而对binder callback的研究,则加深了对上一篇文章源码阅读心得的理解。

    1. 看到某个func() 内部有mRemote/remote()就是客户端拿到BinderProxy/BpBinder;
    2. func() 都需要将descriptor写入Parcel JNI。如果是获取Ibinder则需要额外写入Binder token, 然后mRemote.transact()/remote->transact()就是调用binder驱动;
    3. 看到onTransact()就代表已经在server端,onTransact() switch case会从binder驱动的返回结果中parcel解析出一般数据,如果是获取IBinder就是BinderProxy/BpBinder对象;这中间就会调用真实的func(),最后写回去;
    4. 一般某个文件中会同时有func()2个同名的,要注意区分他的类作用域,一个是Stub/BnXX实体操作;一个是Proxy/BpXX代理操作。

    之前的理解是没有错误的,但是本文理解binder callback后,需要做出解释。
    因为原本的服务端持有了客户端给过来的Callback(IBinder)对象以后,在使用callback的时候,此刻就作为客户端了,反过来,接受的时候,客户端就作为server了。

引用

https://www.cnblogs.com/suncoolcat/p/3329082.html

一篇android匿名内存共享机制原理解析文章;

我之前的2-3篇Binder、AIDL文章。

附录

title camera open流程图

App进程->App进程: java open(id)
App进程->App进程: JNI Camera:connect(id)
App进程–>service: cs.connect(c, id)
service–>App进程: c->mCamera = BpCamera
App进程->App进程: sp (Camera:ICameraClient)

title camera take picture流程
App进程->App进程: android_hardware_Camera_takePicture
App进程->JNI: android_hardware_Camera_takePicture
JNI->ICameraClient: sp takePicture()
ICameraClient->ICameraClient: sp takePicture() transact()
ICameraClient–>CameraService: onTransact()
CameraService->CameraService: CameraService::Client::takePicture()
CameraService->Hardware: enableMsgType
CameraService->Hardware:takePicture()
Hardware–>Hardware:pictureThread mDataCb
Hardware->CameraService:CameraService::Client::dataCallback(sp&)
CameraService->CameraService:handleCompressedPicture(msgType dataPtr)
CameraService–>ICameraClient:dataCallback(msgType mem)
ICameraClient->ICameraClient:dataCallback(msgType mem)
ICameraClient->JNI:listener->postData(msgType, dataPtr)
JNI->JNI:copyAndPost(env, dataPtr, msgType);
JNI->App进程:postEventFromNative(camera_ref,what, arg1,arg2,obj)
App进程->App进程:onPictureTaken((byte[])msg.obj, mCamera)

猜你喜欢

转载自blog.csdn.net/jzlhll123/article/details/80870675