Android Camera系统结构

说到架构/构架就觉得心虚,感觉这两个字太高端了,所以我只敢用结构来表达。

以下表述如果存在错误,请指教!

之前有在一篇博客中提到Camera相关的东西
http://guoh.ai/lifelog/2012/09/asop-camera-source-code-reading-video-snapshot/

比如

$ANDROID_SRC_HOME/frameworks/base/core/java/android/hardware/Camera.java
$ANDROID_SRC_HOME/frameworks/base/core/jni/android_hardware_Camera.cpp

// 注:目前以下这两个文件在JB当中应该被移动位置了,由此可见Android也是个活跃的项目

$ANDROID_SRC_HOME/frameworks/base/include/camera/Camera.h
$ANDROID_SRC_HOME/frameworks/base/libs/camera/Camera.cpp

这篇博客是阅读http://source.android.com/devices/camera.html,结合在JB代码的基础上写的笔记,可能不是那么详细和有逻辑,只关注重点和要注意的地方,所以如果你刚好读到,但是你又没有什么Android Camera的基础的话,建议还是先看看http://developer.android.com/guide/topics/media/camera.htmlReference

正式开始
大体来说Camera分为这么多部分
APP — JNI — NATIVE — BINDER — CAMERA SERVICE — HAL — DRIVER

下面列出这每一部分对应于AOSP当中的代码

APP($ANDROID_SRC_HOME/frameworks/base/core/java/android/hardware/Camera.java)

JNI($ANDROID_SRC_HOME/frameworks/base/core/jni/android_hardware_Camera.cpp)

NATIVE($ANDROID_SRC_HOME/frameworks/av/camera/Camera.cpp)

BINDER($ANDROID_SRC_HOME/frameworks/av/camera/ICameraService.cpp
       $ANDROID_SRC_HOME/frameworks/av/camera/ICameraClient.cpp)

CAMERA SERVICE($ANDROID_SRC_HOME/frameworks/av/services/camera/libcameraservice/CameraService.cpp)

HAL($ANDROID_SRC_HOME/hardware/libhardware/include/hardware/camera.h
    $ANDROID_SRC_HOME/hardware/libhardware/include/hardware/camera_common.h
    $ANDROID_SRC_HOME/frameworks/av/services/camera/libcameraservice/CameraHardwareInterface.h)

对于Galaxy Nexus来讲,HAL的具体实现位于

$ANDROID_SRC_HOME/hardware/ti/omap4xxx/camera/CameraHal.cpp
$ANDROID_SRC_HOME/hardware/ti/omap4xxx/camera/CameraHalCommon.cpp

头文件位于

$ANDROID_SRC_HOME/hardware/ti/omap4xxx/camera/inc/

对于Galaxy Nexus来讲

DRIVER(https://developers.google.com/android/nexus/drivers)

不知道看了对Camera有没有个大体的认识?
大致了解了的话,就翻代码出来看看自己感兴趣的部分吧

你可以通过repo拉取所有代码到本地看,也可以在线查看。
android/hardware/Camera.java

public static Camera open(int cameraId) {
    return new Camera(cameraId);
}
Camera(int cameraId) {
    mShutterCallback = null;
    mRawImageCallback = null;
    mJpegCallback = null;
    mPreviewCallback = null;
    mPostviewCallback = null;
    mZoomListener = null;

    Looper looper;
    if ((looper = Looper.myLooper()) != null) {
        mEventHandler = new EventHandler(this, looper);
    } else if ((looper = Looper.getMainLooper()) != null) {
        mEventHandler = new EventHandler(this, looper);
    } else {
        mEventHandler = null;
    }

    native_setup(new WeakReference<Camera>(this), cameraId);
}

以上代码描述了我们所要研究的东西的基础,开启camera,跟所有的硬件一样,使用之前需要先开启。
这里有个需要注意的地方就是mEventHandler,这个变量是做什么用的,文档有解释的比较清楚,如下:

Callbacks from other methods are delivered to the event loop of the
thread which called open(). If this thread has no event loop, then
callbacks are delivered to the main application event loop. If there
is no main application event loop, callbacks are not delivered.

如果你不是写一个玩具程序的话,你就应当注意这里的描述。

随着native_setup(new WeakReference(this), cameraId);,代码走到了
android_hardware_Camera.cpp

// connect to camera service
static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
    jobject weak_this, jint cameraId)
{
    sp<Camera> camera = Camera::connect(cameraId);

    if (camera == NULL) {
        jniThrowRuntimeException(env, "Fail to connect to camera service");
        return;
    }

    // make sure camera hardware is alive
    if (camera->getStatus() != NO_ERROR) {
        jniThrowRuntimeException(env, "Camera initialization failed");
        return;
    }

    jclass clazz = env->GetObjectClass(thiz);
    if (clazz == NULL) {
        jniThrowRuntimeException(env, "Can't find android/hardware/Camera");
        return;
    }

    // We use a weak reference so the Camera object can be garbage collected.
    // The reference is only used as a proxy for callbacks.
    sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera);
    context->incStrong(thiz);
    camera->setListener(context);

    // save context in opaque field
    env->SetIntField(thiz, fields.context, (int)context.get());
}

随着sp camera = Camera::connect(cameraId);,代码走到了
av/include/camera/Camera.h,当然实现都在av/camera/Camera.cpp

sp<Camera> Camera::connect(int cameraId)
{
    ALOGV("connect");
    sp<Camera> c = new Camera();
    const sp<ICameraService>& cs = getCameraService();
    if (cs != 0) {
        c->mCamera = cs->connect(c, cameraId);
    }
    if (c->mCamera != 0) {
        c->mCamera->asBinder()->linkToDeath(c);
        c->mStatus = NO_ERROR;
    } else {
        c.clear();
    }
    return c;
}

这里getCameraService()是一个比较关键的东西,需要仔细看看。

// establish binder interface to camera service
const sp<ICameraService>& Camera::getCameraService()
{
    Mutex::Autolock _l(mLock);
    if (mCameraService.get() == 0) {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder;
        do {
            binder = sm->getService(String16("media.camera"));
            if (binder != 0)
                break;
            ALOGW("CameraService not published, waiting...");
            usleep(500000); // 0.5 s
        } while(true);
        if (mDeathNotifier == NULL) {
            mDeathNotifier = new DeathNotifier();
        }
        binder->linkToDeath(mDeathNotifier);
        mCameraService = interface_cast<ICameraService>(binder);
    }
    ALOGE_IF(mCameraService==0, "no CameraService!?");
    return mCameraService;
}

然后就是av/camera/ICameraService.cpp当中代理类BpCameraService

// connect to camera service
virtual sp<ICamera> connect(const sp<ICameraClient>& cameraClient, int cameraId)
{
    Parcel data, reply;
    data.writeInterfaceToken(ICameraService::getInterfaceDescriptor());
    data.writeStrongBinder(cameraClient->asBinder());
    data.writeInt32(cameraId);
    remote()->transact(BnCameraService::CONNECT, data, &reply);
    return interface_cast<ICamera>(reply.readStrongBinder());
}

最终转移到BnCameraServiceconnect方法,但是这是个纯虚函数,而且CameraService继承了BnCameraService,所以请看
av/services/camera/libcameraservice/CameraService.h,当然具体实现还是在av/services/camera/libcameraservice/CameraService.cpp当中。

sp<ICamera> CameraService::connect(
        const sp<ICameraClient>& cameraClient, int cameraId) {
    int callingPid = getCallingPid();

    LOG1("CameraService::connect E (pid %d, id %d)", callingPid, cameraId);

    if (!mModule) {
        ALOGE("Camera HAL module not loaded");
        return NULL;
    }

    sp<Client> client;
    if (cameraId < 0 || cameraId >= mNumberOfCameras) {
        ALOGE("CameraService::connect X (pid %d) rejected (invalid cameraId %d).",
            callingPid, cameraId);
        return NULL;
    }

    char value[PROPERTY_VALUE_MAX];
    property_get("sys.secpolicy.camera.disabled", value, "0");
    if (strcmp(value, "1") == 0) {
        // Camera is disabled by DevicePolicyManager.
        ALOGI("Camera is disabled. connect X (pid %d) rejected", callingPid);
        return NULL;
    }

    Mutex::Autolock lock(mServiceLock);
    if (mClient[cameraId] != 0) {
        client = mClient[cameraId].promote();
        if (client != 0) {
            if (cameraClient->asBinder() == client->getCameraClient()->asBinder()) {
                LOG1("CameraService::connect X (pid %d) (the same client)",
                     callingPid);
                return client;
            } else {
                ALOGW("CameraService::connect X (pid %d) rejected (existing client).",
                      callingPid);
                return NULL;
            }
        }
        mClient[cameraId].clear();
    }

    if (mBusy[cameraId]) {
        ALOGW("CameraService::connect X (pid %d) rejected"
                " (camera %d is still busy).", callingPid, cameraId);
        return NULL;
    }

    struct camera_info info;
    if (mModule->get_camera_info(cameraId, &info) != OK) {
        ALOGE("Invalid camera id %d", cameraId);
        return NULL;
    }

    int deviceVersion;
    if (mModule->common.module_api_version == CAMERA_MODULE_API_VERSION_2_0) {
        deviceVersion = info.device_version;
    } else {
        deviceVersion = CAMERA_DEVICE_API_VERSION_1_0;
    }

    switch(deviceVersion) {
      case CAMERA_DEVICE_API_VERSION_1_0:
        client = new CameraClient(this, cameraClient, cameraId,
                info.facing, callingPid, getpid());
        break;
      case CAMERA_DEVICE_API_VERSION_2_0:
        client = new Camera2Client(this, cameraClient, cameraId,
                info.facing, callingPid, getpid());
        break;
      default:
        ALOGE("Unknown camera device HAL version: %d", deviceVersion);
        return NULL;
    }

    if (client->initialize(mModule) != OK) {
        return NULL;
    }

    cameraClient->asBinder()->linkToDeath(this);

    mClient[cameraId] = client;
    LOG1("CameraService::connect X (id %d, this pid is %d)", cameraId, getpid());
    return client;
}

比如我们这里以HAL 1.0来看,参见
av/services/camera/libcameraservice/CameraClient.h

av/services/camera/libcameraservice/CameraClient.cpp

CameraClient::CameraClient(const sp<CameraService>& cameraService,
        const sp<ICameraClient>& cameraClient,
        int cameraId, int cameraFacing, int clientPid, int servicePid):
        Client(cameraService, cameraClient,
                cameraId, cameraFacing, clientPid, servicePid)
{
    int callingPid = getCallingPid();
    LOG1("CameraClient::CameraClient E (pid %d, id %d)", callingPid, cameraId);

    mHardware = NULL;
    mMsgEnabled = 0;
    mSurface = 0;
    mPreviewWindow = 0;
    mDestructionStarted = false;

    // Callback is disabled by default
    mPreviewCallbackFlag = CAMERA_FRAME_CALLBACK_FLAG_NOOP;
    mOrientation = getOrientation(0, mCameraFacing == CAMERA_FACING_FRONT);
    mPlayShutterSound = true;
    LOG1("CameraClient::CameraClient X (pid %d, id %d)", callingPid, cameraId);
}
status_t CameraClient::initialize(camera_module_t *module) {
    int callingPid = getCallingPid();
    LOG1("CameraClient::initialize E (pid %d, id %d)", callingPid, mCameraId);

    char camera_device_name[10];
    status_t res;
    snprintf(camera_device_name, sizeof(camera_device_name), "%d", mCameraId);

    mHardware = new CameraHardwareInterface(camera_device_name);
    res = mHardware->initialize(&module->common);
    if (res != OK) {
        ALOGE("%s: Camera %d: unable to initialize device: %s (%d)",
                __FUNCTION__, mCameraId, strerror(-res), res);
        mHardware.clear();
        return NO_INIT;
    }

    mHardware->setCallbacks(notifyCallback,
            dataCallback,
            dataCallbackTimestamp,
            (void *)mCameraId);

    // Enable zoom, error, focus, and metadata messages by default
    enableMsgType(CAMERA_MSG_ERROR | CAMERA_MSG_ZOOM | CAMERA_MSG_FOCUS |
                  CAMERA_MSG_PREVIEW_METADATA | CAMERA_MSG_FOCUS_MOVE);

    LOG1("CameraClient::initialize X (pid %d, id %d)", callingPid, mCameraId);
    return OK;
}

当然还会用到
libhardware//include/hardware/camera.h

libhardware//include/hardware/camera_common.h

av/services/camera/libcameraservice/CameraHardwareInterface.h

status_t initialize(hw_module_t *module)
{
    ALOGI("Opening camera %s", mName.string());
    int rc = module->methods->open(module, mName.string(),
                                   (hw_device_t **)&mDevice);
    if (rc != OK) {
        ALOGE("Could not open camera %s: %d", mName.string(), rc);
        return rc;
    }
    initHalPreviewWindow();
    return rc;
}

这只是一个接口,具体的实现是厂商来做的啦

以上是从上层调用到底层,接着我们还会看一个从底层调用到上层的情况。

从代码看出有绑定3个callback到driver

mHardware->setCallbacks(notifyCallback,
        dataCallback,
        dataCallbackTimestamp,
        (void *)mCameraId);

所以这三个callback会有机会被call到
以dataCallback为例子,其中有一个是

// picture callback - compressed picture ready
void CameraClient::handleCompressedPicture(const sp<IMemory>& mem) {
    disableMsgType(CAMERA_MSG_COMPRESSED_IMAGE);

    sp<ICameraClient> c = mCameraClient;
    mLock.unlock();
    if (c != 0) {
        c->dataCallback(CAMERA_MSG_COMPRESSED_IMAGE, mem, NULL);
    }
}

通过ICameraClient调用会app,这里的ICameraClient是BpCameraClient(为什么是Bp,后面会稍加解释)

// generic data callback from camera service to app with image data
void BpCameraClient::dataCallback(int32_t msgType, const sp<IMemory>& imageData,
                  camera_frame_metadata_t *metadata)
{
    ALOGV("dataCallback");
    Parcel data, reply;
    data.writeInterfaceToken(ICameraClient::getInterfaceDescriptor());
    data.writeInt32(msgType);
    data.writeStrongBinder(imageData->asBinder());
    if (metadata) {
        data.writeInt32(metadata->number_of_faces);
        data.write(metadata->faces, sizeof(camera_face_t) * metadata->number_of_faces);
    }
    remote()->transact(DATA_CALLBACK, data, &reply, IBinder::FLAG_ONEWAY);
}

于是

status_t BnCameraClient::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
        case NOTIFY_CALLBACK: {
            ...
        } break;
        case DATA_CALLBACK: {
            ALOGV("DATA_CALLBACK");
            CHECK_INTERFACE(ICameraClient, data, reply);
            int32_t msgType = data.readInt32();
            sp<IMemory> imageData = interface_cast<IMemory>(data.readStrongBinder());
            camera_frame_metadata_t *metadata = NULL;
            if (data.dataAvail() > 0) {
                metadata = new camera_frame_metadata_t;
                metadata->number_of_faces = data.readInt32();
                metadata->faces = (camera_face_t *) data.readInplace(
                        sizeof(camera_face_t) * metadata->number_of_faces);
            }
            dataCallback(msgType, imageData, metadata); // 因为pure virtual function的关系,所以会call到Camera当中的实现
            if (metadata) delete metadata;
            return NO_ERROR;
        } break;
        case DATA_CALLBACK_TIMESTAMP: {
            ...
        } break;
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
}

于是

// callback from camera service when frame or image is ready
void Camera::dataCallback(int32_t msgType, const sp<IMemory>& dataPtr,
                          camera_frame_metadata_t *metadata)
{
    sp<CameraListener> listener;
    {
        Mutex::Autolock _l(mLock);
        listener = mListener;
    }
    if (listener != NULL) {
        listener->postData(msgType, dataPtr, metadata);
    }
}

从app call到service是BnCameraClient(client是实体,service是代理,从new的代码就可以观察出来)
从service call到app是BpCameraClient(service是实体,client是代理)
而这个Bn到Bp的转换是通过

sp<ICameraClient> cameraClient = interface_cast<ICameraClient>(data.readStrongBinder());

完成的。

后面我们会列举些需要注意的点:

ICameraClient继承自IInterface
BnCameraClient继承自BnInterface,然而BnInterface又继承自ICameraClient,BBinder

Camera继承自BnCameraClient

ICameraService继承自IInterface
BnCameraService继承自BnInterface,然而BnInterface又继承自ICameraService,BBinder

ICamera继承自IInterface
BnCamera继承自BnInterface,然而BnInterface又继承自ICamera,BBinder

CameraService继承自BinderService,BnCameraService,IBinder::DeathRecipient
而BinderService主要是用来实例化service的统一接口

CameraService有个嵌套类Client继承自BnCamera
真正的实现大部分在CameraClient当中(Camera2Client是Google提供的调用新的HAL的一种方式,部分厂商会用这样的方式来实现自己的HAL,比如Qualcomm)
这才是真正的client,直接调用HW或者被HW调用。

sendCommand往driver送参数的时候会先检验是不是CameraService这层可以处理的东西(有些东西不需要到driver的)

cmd == CAMERA_CMD_SET_DISPLAY_ORIENTATION
cmd == CAMERA_CMD_ENABLE_SHUTTER_SOUND
cmd == CAMERA_CMD_PLAY_RECORDING_SOUND
cmd == CAMERA_CMD_SET_VIDEO_BUFFER_COUNT
cmd == CAMERA_CMD_PING
以上这些都CameraService自己处理了
其他均丢给driver(mHardware->sendCommand(cmd, arg1, arg2);)

接下来是一些疑问点:

// these are initialized in the constructor.
sp mHardware; // cleared after disconnect()
mHardware在disconnect当中被clear

// Make sure disconnect() is done once and once only, whether it is called
// from the user directly, or called by the destructor.
if (mHardware == 0) return;
这是防止disconnect调用一次之后,析构函数又调用一次。

在Linux上开启Core dump来调试

这是一篇原来使用Core dump的记录,整理资料的时候看到的,没有深入的分析,只是用法

基本知识不清楚的话,请在网络上搜寻查阅
http://en.wikipedia.org/wiki/Core_dump
http://en.wikipedia.org/wiki/GNU_Debugger

guohai@KNIGHT:~$ ulimit -c
0
guohai@KNIGHT:~$ ulimit -c unlimited
guohai@KNIGHT:~$ ulimit -c
unlimited
guohai@KNIGHT:~$ ./a.out 
Floating point exception (core dumped)

a.out是需要分析的程序,以上命令就是Linux上使用方法,很简单

更多情况请参考
http://www.cppblog.com/kongque/archive/2011/03/07/141262.aspx

那么在Android上怎么开启呢(首先得有root权限)?

$ adb remount
$ adb shell
root@android:/ # ulimit -c                                                     
unlimited

更改Core dump档案存储的路径(这个存储的路径可以根据需要定制)

root@android:/ # echo "/data/coredump/%p_%e" > /proc/sys/kernel/core_pattern

这样当有native crash存在的时候就会出现对应的Core dump档案了
(有时候执行没有生成Core dump,因为没有/data/coredump文件夹也可能导致无法生成Core dump,大概是没有权限创建)

然后就把档案拷贝到宿主机上,用GDB去载入档案,分析出错的原因

———–EOF———–

Dalvik标记贴

https://android.googlesource.com/platform/dalvik.git
Dalvik

https://android.googlesource.com/platform/dalvik.git/+/master/docs/
Dalvik Docs

http://elinux.org/Android_Dalvik_VM

http://en.wikipedia.org/wiki/Dalvik_(software)

Dalvik, Android’s virtual machine, generates significant debate

DEX文件格式分析

http://hi.baidu.com/seucrcr/item/2b988c570cb63c9208be1778
【翻译】Dalvik——如何控制vm

http://hllvm.group.iteye.com/group/topic/17798
[资料] Dalvik VM的JIT编译器的资料堆积(dumping…work in progress)

开发高响应的Android应用

网络上从来没有间断过对Android为什么这么慢的质疑,硬件配置高,但用起来就是不顺,顿卡,等等各种现象。
https://plus.google.com/100838276097451809262/posts/VDkV9XaJRGS
http://mobile.163.com/12/1123/05/8GVKOL7D0011309K_all.html
http://www.evolife.cn/html/2011/62866.html
可以参阅以上一些文章

除了Android的framework本身的设计因素外,那还有哪些地方值得我们注意的呢?
1、控制好APP的thread,UI响应的事情放到UI thread(也就是默认的main thread),其他比较耗时或者
跟UI不紧密的操作放到worker thread(这个需要开发者自己设计实现的一个thread,多半是执行和UI没有
什么关系的操作,并最终把需要显示的数据post回给UI thread),一般它需要快速响应来自UI thread的事
件请求,比如添加一个action(action就是UI thread接收到用户的事件之后需要做的动作)进来,删除某个
action,调整action的优先级。

2、不要让APP的thread爆掉,Java语言当中thread的使用很是简单和方便,正是因为好用所以不要滥用。

3、用户离开APP的时候,不需要再执行的操作就不必要再执行了。因为Android kernal支持完全的多进程,
所以用户离开了可能一些操作还是会占用CPU在执行,这是应当避免的。虽然会增加程序开发的复杂度,但是谁让
你做Android开发呢?

4、适当降低worker thread的优先级,这点很容易被人忽视,默认创建出来的thread的优先级和
main thread是一样,如果不降低它也会跟main thread抢CPU

5、友好的loading提示,这一招是心理战,但是也管用。

当然还有你的程序要写的高效率啦,现在很多人都能抱着能运行就好的心态,bug能不解就不解的心态,
这样怎么可能出高质量的APP的呢?高品质的东西是细细打磨出来的。

PS. 以下一些命令或者工具可能是有帮助的
$ adb shell top -m MAX_PROCESS_COUNT
$ adb shell top -m MAX_THREAD_COUNT -t

例如
$ adb shell top -m 20

Systrace参见
http://developer.android.com/tools/help/systrace.html

Traceview参见
http://developer.android.com/tools/help/traceview.html

Android ExpandableListView放置不同类型的视图

通常的ExpandableListView的group item是一种视图,child item是另外一种视图,同一种item不会出现多种类型的视图,这是默认的设计。但是我们有时候为了实现更为复杂的界面,就需要ExpandableListView支援同一类型的item放置不同类型的视图。其实做法也很简单,就是Adapter需要实现HeterogeneousExpandableList接口的getChildType,getChildTypeCount,getGroupType和getGroupTypeCount这四个方法。因为ExpandableListView为了效率的因素,它的item的视图是reuse的,如果不指定各个item所用视图的类型,ExpandableListView就会默认采用第一个视图,所以如果不实现这四个方法,呈现出来的界面就是错误的,或者程序直接会crash。详细例子可以参见https://github.com/guohai/and-expandable-listview 。效果如下图:
and-expandable-listview
注意点:getChildTypeCount是用到所有不同类型的child item的总数,不管是同一group item下的不同类型,还是不同group item下的不同类型,只要重复就只算一次。getGroupTypeCount也是如此。
getChildType或者getGroupType是返回位置,所以都是从0开始的,不超过最后一个不重复的child或者group item的位置。

Building and Flashing ROM for PandaBoard ES(详细记录版)

之前有写过一篇http://guoh.ai/lifelog/2012/06/building-and-flashing-rom-for-pandaboard-es/
那篇只是记录主要的问题,没有把详细过程记录下来,因为我当初也是参考别人的出来了,所以就觉得没有必要把详细的东西记录下来。
很多同学看了那边文章还是弄不出来,于是我答应周末有空的时候再完整做一个这个过程供大家参考。
因为这个板子是裸板,没有什么窗口可以显示当前板子的状态,所以可能这个难住了一些同学。买一个板子配套的扩展板又太贵了,不划算。
所以我建议如果大家实在搞不出来,也不知道原因的话,可以买根Linux下可用的串口线(如果你用Mac OS的话自己酌情处理),结合PuTTY用着包你瞬间明白烧录ROM其实也不难。
下面开始正文:
一,Build ROM
编译需要X64的操作系统,官方也是这么说的
X32位机器出现如下错误
/lib64/ld-linux-x86-64.so.2: No such file or directory

关于所用源码
官方也说了,pandaboard只支持master的code,所以源码需要是master的

我这里是目前最新的代码,2012-09-08这天的代码

repo init -u https://android.googlesource.com/platform/manifest
repo sync
repo forall -c 'git checkout -b master aosp/master'

安装一个patch(源码不同这个patch就不同哦)
https://developers.google.com/android/nexus/drivers#panda
PandaBoard binaries for Android 4.0.4 (IMM76I to IMM76L)

在X64上开始编译

guohai@KNIGHT:~/dev/src/android/git/panda$ source build/envsetup.sh

including device/asus/grouper/vendorsetup.sh
including device/generic/armv7-a-neon/vendorsetup.sh
including device/generic/armv7-a/vendorsetup.sh
including device/moto/wingray/vendorsetup.sh
including device/samsung/crespo4g/vendorsetup.sh
including device/samsung/crespo/vendorsetup.sh
including device/samsung/maguro/vendorsetup.sh
including device/samsung/toro/vendorsetup.sh
including device/sony/lt26/vendorsetup.sh
including device/ti/panda/vendorsetup.sh
including sdk/bash_completion/adb.bash

guohai@KNIGHT:~/dev/src/android/git/panda$ lunch 

You’re building on Linux

Lunch menu… pick a combo:
1. full-eng
2. full_x86-eng
3. vbox_x86-eng
4. full_mips-eng
5. full_grouper-userdebug
6. mini_armv7a_neon-userdebug
7. mini_armv7a-userdebug
8. full_wingray-userdebug
9. full_crespo4g-userdebug
10. full_crespo-userdebug
11. full_maguro-userdebug
12. full_toro-userdebug
13. full_lt26-userdebug
14. full_panda-userdebug

在此我选择
full_panda-userdebug

Which would you like? [full-eng] full_panda-userdebug

============================================
PLATFORM_VERSION_CODENAME=AOSP
PLATFORM_VERSION=4.0.9.99.999.9999.99999
TARGET_PRODUCT=full_panda
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=arm
TARGET_ARCH_VARIANT=armv7-a-neon
HOST_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-3.0.0-25-generic-x86_64-with-Ubuntu-11.10-oneiric
HOST_BUILD_TYPE=release
BUILD_ID=OPENMASTER
OUT_DIR=out
============================================

我用

guohai@KNIGHT:~/dev/src/android/git/panda$ make -j16

编译花了2个多钟头,偶中途去了趟超市,做了顿饭,整个ROM才编译完成,千万不要傻傻的等哦,找点别的事情干干(当然啦,偶尔过来看看有没有什么意外情况出现build failure是可以的,我已经build很多遍了,所以机器上的编译环境一般没有什么问题,比较放心)

直到build完成,出现
Creating filesystem with parameters:
Size: 268435456
Block size: 4096
Blocks per group: 32768
Inodes per group: 8192
Inode size: 256
Journal blocks: 1024
Label:
Blocks: 65536
Block groups: 2
Reserved block group size: 15
Created filesystem with 970/16384 inodes and 41312/65536 blocks
+ ‘[‘ 0 -ne 0 ‘]’
Install system fs image: out/target/product/panda/system.img
out/target/product/panda/system.img+out/target/product/panda/obj/PACKAGING/recovery_patch_intermediates/recovery_from_boot.p maxsize=274053120 blocksize=4224 total=167387526 reserve=2770944
DroidDoc took 1181 sec. to write docs to out/target/common/docs/doc-comment-check

这样我们需要的ROM就准备好了

二,Flash ROM
1、未插入电源的状态下,拔掉SD卡

插入USB线,串口线(可选)

连接电源

2、执行

guohai@KNIGHT:~/dev/src/android/git/panda/device/ti/panda$ ./usbboot2 ./bootloader.bin 

using built-in 2ndstage.bin
waiting for OMAP44xx device…
reading ASIC ID
CHIP: 4440
IDEN: 0000000000000000000000000000000000000000
MPKH: 0000000000000000000000000000000000000000000000000000000000000000
CRC0: 229e85ba
CRC1: 00000000
sending 2ndstage to target… f0030002
waiting for 2ndstage response…
sending image to target…
looks everything is ok…

注:最后这一句”look everything is ok…”是我自己加在omap4boot源码里的,这里使用的usbboot2就是omap4boot编译出来的

Putty会给出如下反馈
见图01-usbboot.png
01-usbboot

最后一句是Fastboot entered…
就表明板子已经进入了fastboot mode

3、插入SD Card

执行

guohai@KNIGHT:~/dev/src/android/git/panda/device/ti/panda$ fastboot oem format


OKAY [ 0.656s]
finished. total time: 0.656s

Putty会给出如下反馈
见图02-fastboot-oem-format.png
02-fastboot-oem-format

guohai@KNIGHT:~/dev/src/android/git/panda/device/ti/panda$ fastboot flash xloader ./xloader.bin

sending ‘xloader’ (23 KB)…
OKAY [ 0.007s]
writing ‘xloader’…
OKAY [ 0.321s]
finished. total time: 0.328s

见图03-fastboot-flash-xloader.png
03-fastboot-flash-xloader

guohai@KNIGHT:~/dev/src/android/git/panda/device/ti/panda$ fastboot flash bootloader ./bootloader.bin

sending ‘bootloader’ (161 KB)…
OKAY [ 0.015s]
writing ‘bootloader’…
OKAY [ 0.384s]
finished. total time: 0.399s

见图04-fastboot-flash-bootloader.png
04-fastboot-flash-bootloader

验证板子目前是否处于fastboot mode

guohai@KNIGHT:~/dev/src/android/git/panda/device/ti/panda$ fastboot devices

7024000200000001 fastboot

guohai@KNIGHT:~/dev/src/android/git/panda/device/ti/panda$ fastboot erase cache

erasing ‘cache’…
OKAY [103.063s]
finished. total time: 103.063s

guohai@KNIGHT:~/dev/src/android/git/panda/device/ti/panda$ fastboot flash userdata

sending ‘userdata’ (10428 KB)…
OKAY [ 0.585s]
writing ‘userdata’…
OKAY [ 4.786s]
finished. total time: 5.372s

见图05fastboot-erase-cache_flash-userdata.png
05-fastboot-erase-cache_flash-userdata

guohai@KNIGHT:~/dev/src/android/git/panda/device/ti/panda$ fastboot flashall

——————————————–
Bootloader Version…: U-Boot 1.1.4-gedeced79
Baseband Version…..:
Serial Number……..: 7024000200000001
——————————————–
checking product…
OKAY [ 0.001s]
sending ‘boot’ (3750 KB)…
OKAY [ 0.224s]
writing ‘boot’…
OKAY [ 1.993s]
sending ‘recovery’ (4096 KB)…
OKAY [ 0.237s]
writing ‘recovery’…
OKAY [ 2.113s]
sending ‘system’ (163150 KB)…
OKAY [ 9.107s]
writing ‘system’…
OKAY [ 58.941s]
rebooting…

finished. total time: 72.623s

见图06-flash-all.png 07-flash-all-done.png
06-flash-all
07-flash-all-done

到此完成,板子自动重启
稍等一会儿就出现ANDROID的Logo啦

另外据说PLATFORM_VERSION=4.0.9.99.999.9999.99999
就是Jelly Bean,进去之后发现真的就是Jelly Bean

以前编译ICS也是这一样的步骤,只是代码不同,打的patch不同
要参观多图(非高清),请移步http://www.flickr.com/photos/46848122@N08/sets/72157631476114706/

读AOSP的Camera源码之录影中拍摄

写Camera这一系列之前,先把Camera主要的概况介绍下(上一篇没有写,这基本都会是一些比较简单而简短的文字记录)

参照 http://developer.android.com/reference/android/hardware/Camera.html

整个拍照的过程Docs上也有写的很清楚
1. open camera
2. get/set parameters
3. set display orientation
4. set surface
5. start preview
6. take picture
7. 拍照之后preview display会自动停止,如果还想继续拍摄就需要再次执行step 5
8. stop preview
9. release camera

这当中有些是必须的,有些不是

学习Camera最好熟悉几块重要的部分代码,这样比较容易理解,因为这是一个硬件和软件结合比较紧密的话题。
大多数时候我们只需要熟悉各个参数,然后将他们通过set parameter传递给hardware层就能完成我们需要的大部分功能。

$ANDROID_SRC_HOME/frameworks/base/core/java/android/hardware/Camera.java

$ANDROID_SRC_HOME/frameworks/base/core/jni/android_hardware_Camera.cpp

$ANDROID_SRC_HOME/frameworks/base/include/camera/Camera.h
$ANDROID_SRC_HOME/frameworks/base/libs/camera/Camera.cpp

这几个文件已经基本可以让我们从APP的层面了解Camera了

Camera最重要的任务就是拍摄出最好的照片,APP的parameter设置下去,发现出来的片子不是我们想要的,这时就要分析是我们参数下错了,还是底层出错了。
这都是我们APP这层需要考虑的。

对于用户来说,最理想的状态就是什么都不用设置,按下拍摄,就能出来最好的片子,这就是为什么卡片机被发明出来,而且还这么受到欢迎,虽然卡片机还是无法做到什么都不设置,就能出来最佳效果的片子。

概况介绍就到这里,很简单。
下面接着说今天的主题,录影中拍摄

如今Samsung和HTC都打出边录边拍的功能,对于APP来说实现这种功能不难,只要机器的芯片支持这个功能,对于APP来说就比较简单了。

这个功能的学名叫做Video Snapshot

Android有这么一个API

    /**
     * Returns true if video snapshot is supported. That is, applications
     * can call {@link #takePicture(Camera.ShutterCallback,
     * Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)}
     * during recording. Applications do not need to call {@link
     * #startPreview()} after taking a picture. The preview will be still
     * active. Other than that, taking a picture during recording is
     * identical to taking a picture normally. All settings and methods
     * related to takePicture work identically. Ex: {@link
     * #getPictureSize()}, {@link #getSupportedPictureSizes()}, {@link
     * #setJpegQuality(int)}, {@link #setRotation(int)}, and etc. The
     * picture will have an EXIF header. {@link #FLASH_MODE_AUTO} and {@link
     * #FLASH_MODE_ON} also still work, but the video will record the flash.
     *
     * Applications can set shutter callback as null to avoid the shutter
     * sound. It is also recommended to set raw picture and post view
     * callbacks to null to avoid the interrupt of preview display.
     *
     * Field-of-view of the recorded video may be different from that of the
     * captured pictures.
     *
     * @return true if video snapshot is supported.
     */
android.hardware.Camera.Parameters.isVideoSnapshotSupported()

这个API的注释把使用过程都说的很清楚,跟普通拍摄没什么两样,需要注意的就是在做Video Snapshot的时候不需要start preview就可以拍摄,因为录影的过程中,整个preview一直是活跃的。不会像普通的拍摄一样,每拍摄一次preview就会停止,需要我们再次start preview。

这项功能的关键在于厂商的芯片的好坏,比如能否在录影过程中的拍摄也支持full resolution等等。

AOSP的Camera当中就通过这行代码来实现这个功能

Log.v(TAG, "Video snapshot start");
mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc));

更多可以参见

$ANDROID_SRC_HOME/framework/packages/apps/Camera/src/com/android/camera/VideoCamera.java

很简单不是?

读AOSP的Camera源码之Camera Button启动Camera APP

打算把平时阅读AOSP Camera源码的过程记录下,有些地方没有很精确的去揣摩,所以如果理解有误,烦请大家指出!

Camera Button启动APP,这个比较简单。原理就是当你长按Camera Button的时候,系统会发出一个Broadcast,所以APP只需要注册一个Receiver来接收这个broadcast message就好,当收到这个消息后就去启动APP。

直接用代码说明

Manifest当中有这段配置

<receiver android:name="com.android.camera.CameraButtonIntentReceiver">
    <intent-filter>
        <action android:name="android.intent.action.CAMERA_BUTTON"/>
    </intent-filter>
</receiver>

这个CameraButtonIntentReceiver中会去启动APP,但是它里面有个判断,先判断Hardware可不可以用,如果可以用,就去启动APP,不可以用就什么都不做,直接return掉。好处大概就是防止当你系统运行了很多指令之后,发现HW不可以用,尽早的把错误告诉用户,这是一个良好的程序设计思路。

这里需要关注的就是CameraHolder,它的作用级是保持一个android.hardware.Camera实例,并且记录有多少“用户”打开了android.hardware.Camera这个资源,从它的mUsers属性可以看出。
CameraHolder这个有个小小的特殊的地方,就是如果你有call它的keep方法,它会在你call它的release方法的时候短暂的保持android.hardware.Camera不被释放掉(这里是3秒),这时如果你再次调用open方法,就会节省一些不必要的开销。

这里开启的过程就不说了,比较简单明了。Receiver中一个startActivity就完成了。

用Eclipse编译AOSP中的App或者把自己的App加入到ROM

正如标题所说,本文分为两个部分。比较熟悉Android工程的同学应该都会,老鸟可以完全略过此文。

一、用Eclipse来编译AOSP(Android Open Source Project)中的某个App
Google好心或者出于商业原因,把一些系统自带的App都开源了,如果我们想研究某个App,但是又不想为此每次去编译整个ROM(因为有些系统自带的App会用到一些Internal或者Hidden的API,无法直接把App的源码导入到Eclipse当中用标准SDK编译通过),那我们该怎么办呢?
以Camera为例,在Eclipse中直接通过已有源码创建一个工程之后可能会出现如下错误:
android.filterpacks.*中的很多Class找不到
android.hardware.CameraSound这个Class找不到
想想这些找不到的Class应该是存在于某个地方,只是我们通过SDK来编译的时候找不到,否则为什么在整个ROM编译的时候能通过。
所以我们要做的就是把这些“特殊”的Class都引入到Eclipse的编译环境中来。
Google或者动动脑筋在本地机器上搜索搜索缺失的Class的名字,应该能找出来对应的jar包再哪(这些jar包都是我们编译整个ROM的时候生成的,只是没有被添加到发行的SDK里面,所以我们得成功编译整个ROM至少一次,Ubuntu 11.10 X64编译ROM可以参照《在Ubuntu 11.10 X64上编译Android源码》,最好是参照官方说明),然后引入进来。
在ICS当中,编译Camera需要额外引入的jar包有这么两个:
path/to/aosp/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
path/to/aosp/out/target/common/obj/JAVA_LIBRARIES/filterfw_intermediates/classes.jar
注意在Order and Export中拉高新加入的这两个Library的顺序,要不然还是会因为某些方法或者属性缺失编译不过。
拉高之后的顺序如下图
aosp-app-eclipse-order-and-export
上图中的Hidden_API就是指的这两个jar包。
这样Camera这个App就应该可以在Eclipse当中编译通过了,当然这里说的只是编译apk,要真正运行可能还需要有so文件,也就是编译native code生成的,可以参考其他的说明,编译native code的方法这里不再赘述。

二、把自己的App加入到ROM build当中
ROM中自带的App都是放在/system/app/当中,so文件是放在/system/lib/当中,那么我们如何把自己的App也加入到系统中呢
很简单,两个步骤
1、把源码放到path/to/aosp/packages/apps当中
比如新加的一个App叫Camera2,即这个路径path/to/aosp/packages/apps/Camera2
2、在path/to/aosp/build/target/product/core.mk中增加自己的App
只需要加到PRODUCT_PACKAGES这个变量中就可以了(名字根据path/to/aosp/packages/apps/Camera2/Android.mk当中的LOCAL_PACKAGE_NAME来定)

完成了这两步之后,再编译整个ROM,就会发现你的App已经增加到系统当中了。

Building and Flashing ROM for PandaBoard ES

按照说明进行烧录自己build的ROM到板子上,具体build的方法参见
http://source.android.com/source/building-kernels.html
http://source.android.com/source/building.html
这个过程比较简单

接着参照
$ANDROID_GIT_HOME/device/ti/panda/README
连接好需要的线缆
RS232串口线不是必须的,但是有了它并通过putty之类的工具很容易的观察板子处于什么状态,如果你特别有经验可以不用这个,一般开始弄的话,有它比较方便
putty操作很简单,选中Serial并设置
line /dev/ttyUSB0/
speed 115200
就可以了

开始烧录的时候就会出现很多的问题了,首先PandaBoard是没有embedded的存储区来存放bootloader这些程序
所以需要把这些基础的程序烧到SD卡上

第一个要用到的工具就是usbboot,从usb载入bootloader,但是不幸的是aosp原生提供的usbboot是X64的,在32位的机器下无法使用

好在有omap4boot

git clone https://github.com/swetland/omap4boot/
cd omap4boot
export PATH=$(TOOL_CHAIN_HOME)/prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin:$PATH
把toolchain加入到当前PATH变量中开始build,但是可能会出现SIGNATURE VERIFICATION FAILED
这个时候就需要按照
https://github.com/swetland/omap4boot/issues/7
来解决这个问题

这样就可以让板子进入fastboot模式,详细可以参考
http://android-yfchung.blogspot.com/2011/11/android-40-ice-cream-sandwich-on.html

但是README当中有说到
hold GPIO_121 and press PWRON_RESET
就可以进入fastboot模式,但是我折腾了很久,表示很难进入,还期望哪位高人指点下。
我最终的办法是按照http://android-yfchung.blogspot.com/2011/11/android-40-ice-cream-sandwich-on.html当中说的usbboot就已经进入fastboot模式
也就可以不需要用GPIO_121 + PWRON_RESET

烧录整个ROM的时间会有点长,只要不出错就表示还在执行,所以还是要耐心等待等待。

UPDATE:
2012-09-06
增加文中所提到网页的本站备份文件供无法打开的同学使用
http://guoh.ai/lifelog/wp-content/uploads/2012/09/Android-Developing-Note-Android-4_0-Ice-Cream-Sandwich-on-Pandaboard.zip

2013-07-03
PandaBoard按HW key进入bootloader
前提是必须放一张SD卡(fastboot oem format的或者是在PC上format成FAT或者EXT4都可以)到板子上,然后在板子通电的时候按GPIO_121,就像Eric说的那样,时机很重要,我实验的时候就像在通常PC上面进入BIOS一样,多按几次GPIO_121,应该就可以进入fastboot。
这样就应该可以不需要usbboot了。