Stagefright和codec的交互

Stagefright和codec的交互是通过OpenMAX IL来完成的。

OpenMAX IL的规范头文件位于
/path/to/aosp/frameworks/native/include/media/openmax

概述下OpenMAX IL,它包括客户端(Client),组件(Component),端口(Port),隧道化(Tunneled)。
Client是调用Component的对象,Port和Tunneled是Component之间通信的方式,Component之间还可以存在私有的通信方式。
组件(Component)的功能和其定义的端口类型密切相关,通常情况下:只有一个输出端口的,为Source组件;只有一个输入端口的,为Sink组件;有多个输入端口,一个输出端口的为Mux组件;有一个输入端口,多个输出端口的为DeMux组件;输入输出端口各一个组件的为中间处理环节,这是最常见的组件。

在Android 2.2以及之后,Stagefright是作为OpenMAX IL的上层存在的,它跟OpenMAX IL对接,然后OpenMAX IL又封装了具体的codec。

之前我有在http://guoh.org/lifelog/2013/06/android-mediarecorder-architecture/分析过StagefrightRecorder,当时跟codec的集成我没有仔细分析。

这篇文章会详细分析下这个逻辑过程。

首先我们会碰到几个关键的类,第一个就是OMX,并且它的构造方法当中会去创建一个OMXMaster,继续追踪下,发现OMXMaster正如其名字所示,是一个管理者角色。
OMXMaster作为管理者,每添加一个plugin进来,都会记录在

KeyedVector<String8, OMXPluginBase *> mPluginByComponentName; // 重复名字的codec不会再让其加进来,也就是它会去确定该plugin支持多少codec,然后记录下来
List<OMXPluginBase *> mPlugins; // 主要负责释放内存(不与业务相关,只管有添加进来的plugin)

OMXMaster到底会加多少个OMXPlugin进来?
以及到底会有多少个OMXMaster?
有多少个OMX就有多少个OMXMaster,那有多少个OMX呢?
只有一个,参见

sp<IOMX> MediaPlayerService::getOMX() {
    Mutex::Autolock autoLock(mLock);

    if (mOMX.get() == NULL) {
        mOMX = new OMX;
    }

    return mOMX;
}

那会有多少个OMXPlugin呢?。

一个SoftOMXPlugin,当中可以加载很多codec(s)进来
/path/to/aosp/frameworks/av/media/libstagefright/codecs/
有多少soft codec(s)都在上面这个目录

这里需要说明的是真实的codec的类结构,以FLAC为例子(箭头的方向为类继承的方向)
SoftFlacEncoder -> SimpleSoftOMXComponent -> SoftOMXComponent,SoftOMXComponent把OpenMAX IL定义的组件封装成一个C++类,里面有绑定组件操作的方法,但是它实都是以stub方式实现的,也就是说具体的实现在其子类。
SimpleSoftOMXComponent提供一个用于消息传递的ALooper和AHandlerReflector,实现了比如sendCommand,emptyThisBuffer和fillThisBuffer这三个异步方法(为什么只有这三个是异步,因为OpenMAX IL的规范要求这三个是non-blocking的,在我们这里实现的也就是通过looper/handler发送处理消息),以及useBuffer,allocateBuffer和freeBuffer等等一些其他方法。
这样OMXCodec当中针对OpenMAX IL组件的一个操作就会通过IOMX,进而到OMXNodeInstance,最终进入我们SoftOMXComponent里面,刚刚我们有讲过方法具体实现是在其子类,这样只要找到对应的地方就可以了。

下面的这一段实例代码很清晰,以sendCommand为例子

status_t OMXCodec::init() {
    // mLock is held.
    ...
    err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle);
    ...
}
status_t OMX::sendCommand(
        node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) {
    return findInstance(node)->sendCommand(cmd, param);
}
status_t OMXNodeInstance::sendCommand(
        OMX_COMMANDTYPE cmd, OMX_S32 param) {
    Mutex::Autolock autoLock(mLock);

    OMX_ERRORTYPE err = OMX_SendCommand(mHandle, cmd, param, NULL); // 注意这个宏的定义,实际就是去调用组件的SendCommand方法
    return StatusFromOMXError(err);
}
/** Send a command to the component.  This call is a non-blocking call.
    The component should check the parameters and then queue the command
    to the component thread to be executed.  The component thread shall 
    send the EventHandler() callback at the conclusion of the command. 
    This macro will go directly from the application to the component (via
    a core macro).  The component will return from this call within 5 msec.
    
    When the command is "OMX_CommandStateSet" the component will queue a
    state transition to the new state idenfied in nParam.
    
    When the command is "OMX_CommandFlush", to flush a port's buffer queues,
    the command will force the component to return all buffers NOT CURRENTLY 
    BEING PROCESSED to the application, in the order in which the buffers 
    were received.
    
    When the command is "OMX_CommandPortDisable" or 
    "OMX_CommandPortEnable", the component's port (given by the value of
    nParam) will be stopped or restarted. 
    
    When the command "OMX_CommandMarkBuffer" is used to mark a buffer, the
    pCmdData will point to a OMX_MARKTYPE structure containing the component
    handle of the component to examine the buffer chain for the mark.  nParam1
    contains the index of the port on which the buffer mark is applied.

    Specification text for more details. 
    
    @param [in] hComponent
        handle of component to execute the command
    @param [in] Cmd
        Command for the component to execute
    @param [in] nParam
        Parameter for the command to be executed.  When Cmd has the value 
        OMX_CommandStateSet, value is a member of OMX_STATETYPE.  When Cmd has 
        the value OMX_CommandFlush, value of nParam indicates which port(s) 
        to flush. -1 is used to flush all ports a single port index will 
        only flush that port.  When Cmd has the value "OMX_CommandPortDisable"
        or "OMX_CommandPortEnable", the component's port is given by 
        the value of nParam.  When Cmd has the value "OMX_CommandMarkBuffer"
        the components pot is given by the value of nParam.
    @param [in] pCmdData
        Parameter pointing to the OMX_MARKTYPE structure when Cmd has the value
        "OMX_CommandMarkBuffer".     
    @return OMX_ERRORTYPE
        If the command successfully executes, the return code will be
        OMX_ErrorNone.  Otherwise the appropriate OMX error will be returned.
    @ingroup comp
 */
#define OMX_SendCommand(                                    \
         hComponent,                                        \
         Cmd,                                               \
         nParam,                                            \
         pCmdData)                                          \
     ((OMX_COMPONENTTYPE*)hComponent)->SendCommand(         \
         hComponent,                                        \
         Cmd,                                               \
         nParam,                                            \
         pCmdData)                          /* Macro End */
OMX_ERRORTYPE SimpleSoftOMXComponent::sendCommand(
        OMX_COMMANDTYPE cmd, OMX_U32 param, OMX_PTR data) {
    CHECK(data == NULL);

    sp<AMessage> msg = new AMessage(kWhatSendCommand, mHandler->id());
    msg->setInt32("cmd", cmd);
    msg->setInt32("param", param);
    msg->post();

    return OMX_ErrorNone;
}

当然这是异步的话,就还会有需要异步执行的处理函数回来,比如在onMessageReceived方法当中就有类型为kWhatSendCommand,kWhatEmptyThisBuffer和kWhatFillThisBuffer的消息,分别会去调用自己的处理方法。
其他同步的方法调用过程类似,这里我们就不具体再说,对照代码看看应该就能理解!

还有一点需要提一下,就是具体codec被创建的方式和时间,soft codec是在SoftOMXPlugin::makeComponentInstance当中通过调用该codec的createSoftOMXComponent方法来创建出来的,换句话说我们的codec实现必须要包含这样名字的一个方法,对于FLAC就是

android::SoftOMXComponent *createSoftOMXComponent(
        const char *name, const OMX_CALLBACKTYPE *callbacks,
        OMX_PTR appData, OMX_COMPONENTTYPE **component) {
    return new android::SoftFlacEncoder(name, callbacks, appData, component);
}

会有多少个Vendor plugin(即libstagefrighthw)?
答案也是一个,但是它同样也包含有很多个codec(s)进来,以TI的HW为例子:
TIOMXPlugin(即Vendor plugin)在/path/to/aosp/hardware/ti/omap4xxx/libstagefrighthw/这里
但是具体的实现在/path/to/aosp/hardware/ti/omap4xxx/domx/omx_core/(即libOMX_Core.so)当中
假设要实例化一个codec,就需要经过如下步骤:
TIOMXPlugin.cpp(makeComponentInstance) -> OMX_Core_Wrapper.c(TIOMX_GetHandle) -> OMX_Core.c(OMX_GetHandle) -> 动态去load指定的codec的so档案,然后调用它的初始化方法(OMX_ComponentInit),至于这个方法名,应该是遵循规范得来的(规范上有写这么句话,有这么一个error msg code)。

/** The component specified did not have a "OMX_ComponentInit" or
  "OMX_ComponentDeInit entry point */
OMX_ErrorInvalidComponent = (OMX_S32) 0x80001004,

其支持的codec(s)在/path/to/aosp/hardware/ti/omap4xxx/domx/omx_proxy_component/
当然codec最终的实现可能还是在其他地方!

以下这些方法都是OpenMAX IL所定义的于OMX Core有关的标准方法,详见OMX_Core.h,TI在OMX_Core.c当中有去实现这些方法,这些可以说是管理OpenMAX IL这一层所封装的Component的接口,当然包括自身的生命周期的管理,以及Component之间的沟通。

OMX_API OMX_ERRORTYPE OMX_Init
OMX_API OMX_ERRORTYPE OMX_Deinit
OMX_API OMX_ERRORTYPE OMX_ComponentNameEnum
OMX_API OMX_ERRORTYPE OMX_GetHandle
OMX_API OMX_ERRORTYPE OMX_FreeHandle
OMX_API OMX_ERRORTYPE OMX_SetupTunnel
OMX_API OMX_ERRORTYPE OMX_GetContentPipe
OMX_API OMX_ERRORTYPE OMX_GetComponentsOfRole
OMX_API OMX_ERRORTYPE OMX_GetRolesOfComponent

也就是说OMX是操作所有codec的窗口,它透过OMXMaster来管理Soft/Vendor plugin,每个plugin当中有属于自己的codec(s)。
比如典型的,OMX有allocateNode这个方法,这是增加codec时需要的,当然它实质上是去调用OMXMaster -> OMXPlugin -> OMX_Core -> 特定的codec的实例化方法。

status_t OMX::allocateNode(
        const char *name, const sp<IOMXObserver> &observer, node_id *node) {
    Mutex::Autolock autoLock(mLock);

    *node = 0;

    OMXNodeInstance *instance = new OMXNodeInstance(this, observer);

    OMX_COMPONENTTYPE *handle; // 声明一个OMX_COMPONENTTYPE指针

    // 接着makeComponentInstance创建出来,双指针OUT传值
    // 最终会发现在codec当中会去调用这样一个方法(对于TI的HW,这个方法位于omx_proxy_common.c当中)
    // /*Calling Proxy Common Init() */
	// eError = OMX_ProxyCommonInit(hComponent);
	// 它会去初始化OMX_COMPONENTTYPE的各个属性,方法,例如OMX_COMPONENTTYPE::SetCallbacks等等

    OMX_ERRORTYPE err = mMaster->makeComponentInstance(
            name, &OMXNodeInstance::kCallbacks,
            instance, &handle);

    if (err != OMX_ErrorNone) {
        ALOGV("FAILED to allocate omx component '%s'", name);

        instance->onGetHandleFailed();

        return UNKNOWN_ERROR;
    }

    *node = makeNodeID(instance);
    mDispatchers.add(*node, new CallbackDispatcher(instance));

    instance->setHandle(*node, handle);

    mLiveNodes.add(observer->asBinder(), instance);
    observer->asBinder()->linkToDeath(this);

    return OK;
}

有两点值得注意的是
a)

status_t OMXClient::connect() {
    sp<IServiceManager> sm = defaultServiceManager();
    sp<IBinder> binder = sm->getService(String16("media.player"));
    sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);

    // defaultServiceManager(位于/path/to/aosp/frameworks/native/include/binder/IServiceManager.h)
    // 这样调用就跟本地的一个service指针来调用getOMX是一样的,因为本来StatefrightRecorder是在MediaPlayerService当中new出来的
    // 这个我们在之前的研究讨论中做过实验,详细参见http://guoh.org/lifelog/2013/06/sample-of-customized-service-on-android-platform/怎样为Android添加一个系统级服务

    CHECK(service.get() != NULL);

    mOMX = service->getOMX(); // BnOMX
    CHECK(mOMX.get() != NULL);

    // 什么情况会有不是在本地,貌似OMX是在MediaPlayerService当中new出来的,
    // OMXClient是在StagefrightRecorder当中new出来的,而StagefrightRecorder又是在MediaPlayerService当中new出来
    // 所以什么情况下会走到如下的这个using client-side OMX mux当中去(MuxOMX位于OMXClient.cpp当中)?
    if (!mOMX->livesLocally(NULL /* node */, getpid())) {
        ALOGI("Using client-side OMX mux.");
        mOMX = new MuxOMX(mOMX); // 继承自IOMX
    }

    return OK;
}

容易看出OMXClient是获取OMX的窗口

b)

sp<MediaSource> encoder = OMXCodec::Create(
        client.interface(), enc_meta,
        true /* createEncoder */, cameraSource,
        NULL, encoder_flags);

那OMXCodec/
/path/to/aosp/frameworks/av/include/media/stagefright
/path/to/aosp/frameworks/av/media/libstagefright
OMXNodeInstance
/path/to/aosp/frameworks/av/media/libstagefright/include
/path/to/aosp/frameworks/av/media/libstagefright/omx
是做什么的?
OMXCodec,OMXCodecObserver和OMXNodeInstance是一一对应的,
简单的可以理解它们3个构成了OpenMAX IL的一个Component,每一个node就是一个codec在OMX服务端的标识。
当然还有CallbackDispatcher,用于处理codec过来的消息,通过它的post/loop/dispatch来发起接收,最终透过IOMX::onMessage -> OMXNodeInstance::onMessage -> OMXCodecObserver::onMessage -> OMXCodec::on_message一路往上,当然消息的来源是因为我们有向codec注册OMXNodeInstance::kCallbacks,请看

status_t OMX::allocateNode(
        const char *name, const sp<IOMXObserver> &observer, node_id *node) {
    ...
    OMX_ERRORTYPE err = mMaster->makeComponentInstance(
            name, &OMXNodeInstance::kCallbacks,
            instance, &handle);
    ...
    return OK;
}

kCallbacks包含3种事件

OMX_CALLBACKTYPE OMXNodeInstance::kCallbacks = {
    &OnEvent, &OnEmptyBufferDone, &OnFillBufferDone
};

它们分别都会调用到自己owner的OnEvent/OnEmptyBufferDone/OnFillBufferDone

// static
OMX_ERRORTYPE OMXNodeInstance::OnEvent(
        OMX_IN OMX_HANDLETYPE hComponent,
        OMX_IN OMX_PTR pAppData,
        OMX_IN OMX_EVENTTYPE eEvent,
        OMX_IN OMX_U32 nData1,
        OMX_IN OMX_U32 nData2,
        OMX_IN OMX_PTR pEventData) {
    OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData);
    if (instance->mDying) {
        return OMX_ErrorNone;
    }
    return instance->owner()->OnEvent(
            instance->nodeID(), eEvent, nData1, nData2, pEventData);
}

而owner相应的又会调用自己dispatcher的post方法,如下:

OMX_ERRORTYPE OMX::OnEvent(
        node_id node,
        OMX_IN OMX_EVENTTYPE eEvent,
        OMX_IN OMX_U32 nData1,
        OMX_IN OMX_U32 nData2,
        OMX_IN OMX_PTR pEventData) {
    ALOGV("OnEvent(%d, %ld, %ld)", eEvent, nData1, nData2);

    omx_message msg;
    msg.type = omx_message::EVENT;
    msg.node = node;
    msg.u.event_data.event = eEvent;
    msg.u.event_data.data1 = nData1;
    msg.u.event_data.data2 = nData2;

    findDispatcher(node)->post(msg);

    return OMX_ErrorNone;
}

这样所有的事件都串起来了,消息有来源,有最终的去处!

结合这些信息所以我们就可以认为在这里是创建出了一个Component出来。

另外值得提一下的就是我们前面已经讲过Component之间也是可以有信息交互的,比如我们在录影的时候,CameraSource就作为数据的来源被OMXCodec持有,那么从OMXCodec读取的数据实际都是来自于CameraSource,然后就被MediaWriter,例如MPEG4Writer写到文件当中。

OMX_GetComponentVersion
OMX_SendCommand
OMX_GetParameter
OMX_SetParameter
OMX_GetConfig
OMX_SetConfig
OMX_GetExtensionIndex
OMX_GetState
OMX_UseBuffer
OMX_AllocateBuffer
OMX_FreeBuffer
OMX_EmptyThisBuffer
OMX_FillThisBuffer
OMX_UseEGLImage

以上这些macro位于OMX_Core.h当中,这是OpenMax所定义的
针对每一个Component封装,他们负责与具体的codec进行沟通,对于TI的HW,这些方法都实现于omx_proxy_common.c当中

其他内容:
1、HardwareAPI.h
extern android::OMXPluginBase *createOMXPlugin();
定义一个实例化plugin的入口方法,在OMXMaster当中回去动态调用这个方法

2、对于Qualcomm的HW来说,它的libstagefrighthw实现位于如下位置:
libomxcore.so /path/to/aosp/hardware/qcom/media/mm-core/omxcore

3、还有比较关键的就是OpenMAX IL之间本身的原理,还有相互沟通是怎么样的,这个比较重要。
都知道OpenMAX IL最大的作用就是一个适配层作用,它封装了底层Vendor容易变化的部分(不同codec/不同Vendor,或者其他形式的变化),对上层提供一个统一的接口(比如在这里就是Stagefright)。

一个Component的状态分为这么多种,这些定义在OMXCodec当中

enum State {
    DEAD,
    LOADED,
    LOADED_TO_IDLE,
    IDLE_TO_EXECUTING,
    EXECUTING,
    EXECUTING_TO_IDLE,
    IDLE_TO_LOADED,
    RECONFIGURING,
    ERROR
};

Component之间的交互,比如Tunneled communication,是通过Component的ComponentTunnelRequest方法来完成的(TI的HW实现在omx_proxy_common.c当中),但是从代码上看起来在Qualcomm或者TI的Camera这块都没有用到Tunneled communication,Qualcomm根本就没有去实现ComponentTunnelRequest方法。

Buffer的传输过程,这块初看很复杂,其实流程还是比较清楚的。在Camera录影这个场景中,MediaWriter会不断的请求MediaSource的read方法,从中间读取数据,但是在MediaSource内部会对其进行控制,用的就是signal(Condition mFrameAvailableCondition;)。OMXCodec的drainInputBuffer/fillOutputBuffer这两个方法最重要,排空input缓冲(source过来的数据)和填充output缓冲(准备给writer的数据)。drainInputBuffer把数据发到实际的codec,codec收完之后发送EMPTY_BUFFER_DONE消息过来,OMXCodec收到该消息继续发送数据到codec。另外同时OMXCodec还会执行fillOutputBuffer,即喂给codec一个空的buffer,codec编码完成之后会发送FILL_BUFFER_DONE过来,这个消息带有编码好的数据,即被填满的buffer,这样MediaWriter就收到编码完成的数据,然后保存就可以了。细节性的东西就需要自己追踪代码可能更好理解,聪明的你可能就想到了那这里是不是存在两个Buffer,是的,你是对的,请看List mFilledBuffers;

这里再罗嗦的讲解一遍实际过程(MPEG4录影为例子):
MPEG4Writer:
开始read之后,会调用drainInputBuffers(),它会从kPortIndexInput取出一组buffer出来,先把数据从CameraSource读取到这个buffer当中,然后调用IOMX的emptyBuffer把buffer交给实际的encoder,然后while loop等待encoder把output buffer(从kPortIndexOutput取出)填满(encoder的FILL_BUFFER_DONE回来会将通过msg带回来的数据放到output buffer,还会在mFilledBuffers当中记录下该buffer的index,并通过mBufferFilled.signal()广播出来),也就是说OMXCodec的read方法是同步的,必须要等到从CameraSource读取数据,然后encoder编码完毕返回数据才算read结束,然后会MPEG4Writer会将该buffer拷贝一份出来,并且释放掉原来的buffer(这个buffer是大家共同可以访问的,所以MPEG4Writer不想或者不应该长期持有它,写文件所消耗的时间并不是一定的,所以这里应该算是以空间换时间的一种做法)。

buffer的分配:
input buffer的MediaBuffer是在CameraSource当中分配,OMXCodec当中release(EMPTY_BUFFER_DONE回来如果buffer没有被release掉就执行release过程)
output buffer的MediaBuffer分多种情况,有可能是在allocateBuffer的时候分配的,有可能是FILL_BUFFER_DONE回来的时候分配,在MediaWriter读取到处理好之后的数据自己拷贝该数据一份之后release。

End Of Stream:
一般情况decoder或encoder告诉我们EOS,但是有些时候不告诉我们的时候OMXCodec也有做workaround,通过计算不在OMX Component的buffer的个数,如果所有buffer都不被OMX Component所持有,就认为已经到EOS了。

Android标记贴

这里记录些经常遇到的有关Android/AOSP/PandaBoard的问题(可能比较小,并且又不知道放哪里的事情)!

1. PandaBoard获取root privilege(adb remount)

/path/to/aosp/out/target/product/panda/root/default.prop

修改设定为ro.secure=0
再次打包镜像,理论上这应该对所有的ROM都管用!
当然我这里是AOSP源码编译然后烧录的,如果只是普通的ROM,想root的话,请参考官方或者网络上的方法!

Bitmap备忘

这是一篇阅读Bitmap相关内容的笔记!
程序运行环境:

Linux KNIGHT 3.0.0-28-generic #45-Ubuntu SMP Wed Nov 14 21:57:26 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux

BMP图像文件被分成4个部分:位图文件头(Bitmap File Header)、位图信息头(Bitmap Info Header)、颜色表(Color Map)和位图数据(即图像数据,Data Bits或Data Body)。

这本书(http://vipbase.net/ipbook/chap01.htm)第一章节有讲这些。

biBitCount:每个像素所占的位数(bit),其值必须为1(黑白图像)、4(16色图)、8(256色)、24(真彩色图),32(带透明度alpha通道,位于前8位)。

以下这几篇博客有讲些基本的东西。
http://www.cnblogs.com/shengansong/archive/2011/09/23/2186409.html
http://blog.csdn.net/yutianzuijin/article/details/8243343
http://blog.csdn.net/scut1135/article/details/5573395

我有参考以上资料修改出一个可以在x86_64 GNU/Linux编译并运行的程序,注释里面有写些需要注意的地方。

some pitfalls of __attribute__((packed)), plz refer
http://stackoverflow.com/questions/8568432/is-gccs-attribute-packed-pragma-pack-unsafe
http://stackoverflow.com/questions/11770451/what-is-the-meaning-of-attribute-packed-aligned4
http://stackoverflow.com/questions/11667181/why-does-padding-have-to-be-a-power-of-two
http://stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member
http://en.wikipedia.org/wiki/Data_structure_alignment

Linux下BMP转化为JPEG程序源代码
http://www.linuxidc.com/Linux/2011-03/33193.htm

如果你自己要开发JPEG相关的话,确保这些包是有安装的。

sudo apt-get install libjpeg62
sudo apt-get install libjpeg62-dev

Android MediaRecorder系统结构

前面有分析过Camera的实现,现在来看看MediaRecorder的实现,这里我不会太去关注它的分层结构,我更关注它的逻辑!

APP层 /path/to/aosp/frameworks/base/media/java/android/media/MediaRecorder.java
JNI层 /path/to/aosp/frameworks/base/media/jni/android_media_MediaRecorder.cpp
调用NATIVE层的MediaRecorder(这里是BnMediaRecorderClient)
header /path/to/aosp/frameworks/av/include/media/mediarecorder.h
implementation /path/to/aosp/frameworks/av/media/libmedia/mediarecorder.cpp

MediaRecorder::MediaRecorder() : mSurfaceMediaSource(NULL)
{
    ALOGV("constructor");

    const sp<IMediaPlayerService>& service(getMediaPlayerService());
    if (service != NULL) {
        mMediaRecorder = service->createMediaRecorder(getpid());
    }
    if (mMediaRecorder != NULL) {
        mCurrentState = MEDIA_RECORDER_IDLE;
    }

    doCleanUp();
}

getMediaPlayerService()这个方法位于/path/to/aosp/frameworks/av/include/media/IMediaDeathNotifier.h

获取到MediaPlayerService(这个是BpMediaPlayerService)之后
调用IMediaPlayerService当中的

sp<IMediaRecorder> MediaPlayerService::createMediaRecorder(pid_t pid)
{
    sp<MediaRecorderClient> recorder = new MediaRecorderClient(this, pid);
    wp<MediaRecorderClient> w = recorder;
    Mutex::Autolock lock(mLock);
    mMediaRecorderClients.add(w);
    ALOGV("Create new media recorder client from pid %d", pid);
    return recorder;
}

创建MediaRecorderClient(这里是BnMediaRecorder)

但是通过binder拿到的是BpMediaRecorder
因为有如下的interface_cast过程

virtual sp<IMediaRecorder> createMediaRecorder(pid_t pid)
{
    Parcel data, reply;
    data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
    data.writeInt32(pid);
    remote()->transact(CREATE_MEDIA_RECORDER, data, &reply);
    return interface_cast<IMediaRecorder>(reply.readStrongBinder());
}

而MediaRecorderClient当中又会创建StagefrightRecorder(MediaRecorderBase),它位于
/path/to/aosp/frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp

目前我们可以认为在APP/JNI/NATIVE这边是在一个进程当中,在MediaPlayerService当中的MediaRecorderClient/StagefrightRecorder是在另外一个进程当中,他们之间通过binder通信,而且Bp和Bn我们也都有拿到,后面我们将不再仔细区分Bp和Bn。

客户端这边
BnMediaRecorderClient
BpMediaRecorder
BpMediaPlayerService

服务端这边
BpMediaRecorderClient(如果需要通知客户端的话,它可以获得这个Bp)
BnMediaRecorder
BnMediaPlayerService

这有张图(点过去看原始大图)
Android MediaRecorder Diagram

我们以开始录影为例子,比如start()

在这里就兵分两路,一个CameraSource,一个MPEG4Writer(sp mWriter)
这两个class都位于/path/to/aosp/frameworks/av/media/libstagefright/当中

status_t StagefrightRecorder::startMPEG4Recording() {
    int32_t totalBitRate;
    status_t err = setupMPEG4Recording(
            mOutputFd, mVideoWidth, mVideoHeight,
            mVideoBitRate, &totalBitRate, &mWriter);
    if (err != OK) {
        return err;
    }

    int64_t startTimeUs = systemTime() / 1000;
    sp<MetaData> meta = new MetaData;
    setupMPEG4MetaData(startTimeUs, totalBitRate, &meta);

    err = mWriter->start(meta.get());
    if (err != OK) {
        return err;
    }

    return OK;
}
status_t StagefrightRecorder::setupMPEG4Recording(
        int outputFd,
        int32_t videoWidth, int32_t videoHeight,
        int32_t videoBitRate,
        int32_t *totalBitRate,
        sp<MediaWriter> *mediaWriter) {
    mediaWriter->clear();
    *totalBitRate = 0;
    status_t err = OK;
    sp<MediaWriter> writer = new MPEG4Writer(outputFd);

    if (mVideoSource < VIDEO_SOURCE_LIST_END) {

        sp<MediaSource> mediaSource;
        err = setupMediaSource(&mediaSource); // very important
        if (err != OK) {
            return err;
        }

        sp<MediaSource> encoder;
        err = setupVideoEncoder(mediaSource, videoBitRate, &encoder); // very important
        if (err != OK) {
            return err;
        }

        writer->addSource(encoder);
        *totalBitRate += videoBitRate;
    }

    // Audio source is added at the end if it exists.
    // This help make sure that the "recoding" sound is suppressed for
    // camcorder applications in the recorded files.
    if (!mCaptureTimeLapse && (mAudioSource != AUDIO_SOURCE_CNT)) {
        err = setupAudioEncoder(writer); // very important
        if (err != OK) return err;
        *totalBitRate += mAudioBitRate;
    }

    ...

    writer->setListener(mListener);
    *mediaWriter = writer;
    return OK;
}
// Set up the appropriate MediaSource depending on the chosen option
status_t StagefrightRecorder::setupMediaSource(
                      sp<MediaSource> *mediaSource) {
    if (mVideoSource == VIDEO_SOURCE_DEFAULT
            || mVideoSource == VIDEO_SOURCE_CAMERA) {
        sp<CameraSource> cameraSource;
        status_t err = setupCameraSource(&cameraSource);
        if (err != OK) {
            return err;
        }
        *mediaSource = cameraSource;
    } else if (mVideoSource == VIDEO_SOURCE_GRALLOC_BUFFER) {
        // If using GRAlloc buffers, setup surfacemediasource.
        // Later a handle to that will be passed
        // to the client side when queried
        status_t err = setupSurfaceMediaSource();
        if (err != OK) {
            return err;
        }
        *mediaSource = mSurfaceMediaSource;
    } else {
        return INVALID_OPERATION;
    }
    return OK;
}
status_t StagefrightRecorder::setupCameraSource(
        sp<CameraSource> *cameraSource) {
    status_t err = OK;
    if ((err = checkVideoEncoderCapabilities()) != OK) {
        return err;
    }
    Size videoSize;
    videoSize.width = mVideoWidth;
    videoSize.height = mVideoHeight;
    if (mCaptureTimeLapse) {
        if (mTimeBetweenTimeLapseFrameCaptureUs < 0) {
            ALOGE("Invalid mTimeBetweenTimeLapseFrameCaptureUs value: %lld",
                mTimeBetweenTimeLapseFrameCaptureUs);
            return BAD_VALUE;
        }

        mCameraSourceTimeLapse = CameraSourceTimeLapse::CreateFromCamera(
                mCamera, mCameraProxy, mCameraId,
                videoSize, mFrameRate, mPreviewSurface,
                mTimeBetweenTimeLapseFrameCaptureUs);
        *cameraSource = mCameraSourceTimeLapse;
    } else {
        *cameraSource = CameraSource::CreateFromCamera(
                mCamera, mCameraProxy, mCameraId, videoSize, mFrameRate,
                mPreviewSurface, true /*storeMetaDataInVideoBuffers*/);
    }
    mCamera.clear();
    mCameraProxy.clear();
    if (*cameraSource == NULL) {
        return UNKNOWN_ERROR;
    }

    if ((*cameraSource)->initCheck() != OK) {
        (*cameraSource).clear();
        *cameraSource = NULL;
        return NO_INIT;
    }

    // When frame rate is not set, the actual frame rate will be set to
    // the current frame rate being used.
    if (mFrameRate == -1) {
        int32_t frameRate = 0;
        CHECK ((*cameraSource)->getFormat()->findInt32(
                    kKeyFrameRate, &frameRate));
        ALOGI("Frame rate is not explicitly set. Use the current frame "
             "rate (%d fps)", frameRate);
        mFrameRate = frameRate;
    }

    CHECK(mFrameRate != -1);

    mIsMetaDataStoredInVideoBuffers =
        (*cameraSource)->isMetaDataStoredInVideoBuffers();

    return OK;
}
status_t StagefrightRecorder::setupVideoEncoder(
        sp<MediaSource> cameraSource,
        int32_t videoBitRate,
        sp<MediaSource> *source) {
    source->clear();

    sp<MetaData> enc_meta = new MetaData;
    enc_meta->setInt32(kKeyBitRate, videoBitRate);
    enc_meta->setInt32(kKeyFrameRate, mFrameRate);

    switch (mVideoEncoder) {
        case VIDEO_ENCODER_H263:
            enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
            break;

        case VIDEO_ENCODER_MPEG_4_SP:
            enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4);
            break;

        case VIDEO_ENCODER_H264:
            enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
            break;

        default:
            CHECK(!"Should not be here, unsupported video encoding.");
            break;
    }

    sp<MetaData> meta = cameraSource->getFormat();

    int32_t width, height, stride, sliceHeight, colorFormat;
    CHECK(meta->findInt32(kKeyWidth, &width));
    CHECK(meta->findInt32(kKeyHeight, &height));
    CHECK(meta->findInt32(kKeyStride, &stride));
    CHECK(meta->findInt32(kKeySliceHeight, &sliceHeight));
    CHECK(meta->findInt32(kKeyColorFormat, &colorFormat));

    enc_meta->setInt32(kKeyWidth, width);
    enc_meta->setInt32(kKeyHeight, height);
    enc_meta->setInt32(kKeyIFramesInterval, mIFramesIntervalSec);
    enc_meta->setInt32(kKeyStride, stride);
    enc_meta->setInt32(kKeySliceHeight, sliceHeight);
    enc_meta->setInt32(kKeyColorFormat, colorFormat);
    if (mVideoTimeScale > 0) {
        enc_meta->setInt32(kKeyTimeScale, mVideoTimeScale);
    }
    if (mVideoEncoderProfile != -1) {
        enc_meta->setInt32(kKeyVideoProfile, mVideoEncoderProfile);
    }
    if (mVideoEncoderLevel != -1) {
        enc_meta->setInt32(kKeyVideoLevel, mVideoEncoderLevel);
    }

    OMXClient client;
    CHECK_EQ(client.connect(), (status_t)OK);

    uint32_t encoder_flags = 0;
    if (mIsMetaDataStoredInVideoBuffers) {
        encoder_flags |= OMXCodec::kStoreMetaDataInVideoBuffers;
    }

    // Do not wait for all the input buffers to become available.
    // This give timelapse video recording faster response in
    // receiving output from video encoder component.
    if (mCaptureTimeLapse) {
        encoder_flags |= OMXCodec::kOnlySubmitOneInputBufferAtOneTime;
    }

    sp<MediaSource> encoder = OMXCodec::Create(
            client.interface(), enc_meta,
            true /* createEncoder */, cameraSource,
            NULL, encoder_flags);
    if (encoder == NULL) {
        ALOGW("Failed to create the encoder");
        // When the encoder fails to be created, we need
        // release the camera source due to the camera's lock
        // and unlock mechanism.
        cameraSource->stop();
        return UNKNOWN_ERROR;
    }

    *source = encoder;

    return OK;
}

这里和OMXCodec关联起来
有一个叫media_codecs.xml的配置文件来表明设备支持哪些codec

我们录制MPEG 4的时候还会有声音,所以后面还有个setupAudioEncoder,具体的方法就不展开了,总之就是把声音也作为一个Track加入到MPEG4Writer当中去。
这里插个题外话,Google说把setupAudioEncoder放到后面是为了避免开始录影的那一个提示声音也被录制进去,但是实际发现它这样做还是会有bug,在一些设备上还是会把那声录制进去,这个遇到的都是靠APP自己来播放声音来绕过这个问题的。

另外MPEG4Writer当中有个
start(MetaData*)
启动两个方法
a) startWriterThread

启动一个thread去写

    void MPEG4Writer::threadFunc() {
        ALOGV("threadFunc");

        prctl(PR_SET_NAME, (unsigned long)"MPEG4Writer", 0, 0, 0);

        Mutex::Autolock autoLock(mLock);
        while (!mDone) {
            Chunk chunk;
            bool chunkFound = false;

            while (!mDone && !(chunkFound = findChunkToWrite(&chunk))) {
                mChunkReadyCondition.wait(mLock);
            }

            // Actual write without holding the lock in order to
            // reduce the blocking time for media track threads.
            if (chunkFound) {
                mLock.unlock();
                writeChunkToFile(&chunk);
                mLock.lock();
            }
        }

        writeAllChunks();
    }

b) startTracks

    status_t MPEG4Writer::startTracks(MetaData *params) {
        for (List<Track *>::iterator it = mTracks.begin();
             it != mTracks.end(); ++it) {
            status_t err = (*it)->start(params);

            if (err != OK) {
                for (List<Track *>::iterator it2 = mTracks.begin();
                     it2 != it; ++it2) {
                    (*it2)->stop();
                }

                return err;
            }
        }
        return OK;
    }

然后调用每个Track的start方法

    status_t MPEG4Writer::Track::start(MetaData *params) {
        ...

        initTrackingProgressStatus(params);

        ...

        status_t err = mSource->start(meta.get()); // 这里会去执行CameraSource(start),这两个是相互关联的

        ...

        pthread_create(&mThread, &attr, ThreadWrapper, this);
        return OK;
    }

    void *MPEG4Writer::Track::ThreadWrapper(void *me) {
        Track *track = static_cast<Track *>(me);

        status_t err = track->threadEntry();
        return (void *) err;
    }

通过status_t MPEG4Writer::Track::threadEntry()
是新启动另外一个thread,它里面会通过一个循环来不断读取CameraSource(read)里面的数据,CameraSource里面的数据当然是从driver返回过来的(可以参见CameraSourceListener,CameraSource用一个叫做mFrameReceived的List专门存放从driver过来的数据,如果收到数据会调用mFrameAvailableCondition.signal,若还没有开始录影,这个时候收到的数据是被丢弃的,当然MediaWriter先启动的是CameraSource的start方法,再启动写Track),然后写到文件当中。
注意:准确来说这里MPEG4Writer读取的是OMXCodec里的数据,因为数据先到CameraSource,codec负责编码之后,MPEG4Writer才负责写到文件当中!关于数据在CameraSource/OMXCodec/MPEG4Writer之间是怎么传递的,可以参见http://guoh.org/lifelog/2013/06/interaction-between-stagefright-and-codec/当中讲Buffer的传输过程。

回头再来看,Stagefright做了什么事情?我更觉得它只是一个粘合剂(glue)的用处,它工作在MediaPlayerService这一层,把MediaSource,MediaWriter,Codec以及上层的MediaRecorder绑定在一起,这应该就是它最大的作用,Google用它来替换Opencore也是符合其一贯的工程派作风(相比复杂的学术派而言,虽然Google很多东西也很复杂,但是它一般都是以尽量简单的方式来解决问题)。
让大家觉得有点不习惯的是,它把MediaRecorder放在MediaPlayerService当中,这两个看起来是对立的事情,或者某一天它们会改名字,或者是两者分开,不知道~~

当然这只是个简单的大体介绍,Codec相关的后面争取专门来分析一下!

有些细节的东西在这里没有列出,需要的话会把一些注意点列出来:

1. 时光流逝录影
CameraSource对应的就是CameraSourceTimeLapse

具体做法就是在
dataCallbackTimestamp
当中有skipCurrentFrame

当然它是用些变量来记录和计算
mTimeBetweenTimeLapseVideoFramesUs(1E6/videoFrameRate) // 两个frame之间的间隔时间
记录上一个frame的(mLastTimeLapseFrameRealTimestampUs) // 上一个frame发生的时间
然后通过frame rate计算出两个frame之间的相距离时间,中间的都透过releaseOneRecordingFrame来drop掉
也就是说driver返回的东西都不变,只是在SW这层我们自己来处理掉

关于Time-lapse相关的可以参阅
https://en.wikipedia.org/wiki/Time-lapse_photography

2. 录影当中需要用到Camera的话是通过ICameraRecordingProxy,即Camera当中的RecordingProxy(这是一个BnCameraRecordingProxy)
当透过binder,将ICameraRecordingProxy传到服务端进程之后,它就变成了Bp,如下:

case SET_CAMERA: {
    ALOGV("SET_CAMERA");
    CHECK_INTERFACE(IMediaRecorder, data, reply);
    sp<ICamera> camera = interface_cast<ICamera>(data.readStrongBinder());
    sp<ICameraRecordingProxy> proxy =
        interface_cast<ICameraRecordingProxy>(data.readStrongBinder());
    reply->writeInt32(setCamera(camera, proxy));
    return NO_ERROR;
} break;

在CameraSource当中会这样去使用

// We get the proxy from Camera, not ICamera. We need to get the proxy
// to the remote Camera owned by the application. Here mCamera is a
// local Camera object created by us. We cannot use the proxy from
// mCamera here.
mCamera = Camera::create(camera);
if (mCamera == 0) return -EBUSY;
mCameraRecordingProxy = proxy;
mCameraFlags |= FLAGS_HOT_CAMERA;

疑问点:

CameraSource当中这个
List > mFramesBeingEncoded;
有什么用?
每编码完一个frame,CameraSource就会将其保存起来,Buffer被release的时候,会反过来release掉这些frame(s),这种做法是为了效率么?为什么不编码完一个frame就将其release掉?
另外不得不再感叹下Google经常的delete this;行为,精妙,但是看起来反常!

怎样为Android添加一个系统级服务

内容均来自于网络资料,源码以及自己的理解,如有错误的地方还请指出!示例源码可以随意使用。

我这里使用的环境如下

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

Android提供了一些基础服务,但是如果你想给它添加一些特定的服务的话,也是可以的(比如在一直到某些特定的平台下,需要增加/裁剪一些服务)

比如我们都知道Android上用的最广泛的应该是多媒体服务,其对应的有一个mediaserver进程,然后比如我们的
AudioFlinger/MediaPlayerService/CameraService/AudioPolicyService
都是跑在这个进程里面的

那如果我们想自己增加一个类似的东西可不可以呢?答案当然是可以的。

很简单的几点就可以帮你完成

启动进程

/path/to/aosp/frameworks/base/customized_service/server

具体服务实现

/path/to/aosp/frameworks/base/customized_service/libcustomizedservice

系统开机启动

/path/to/aosp/system/core/rootdir/init.rc

添加如下内容到init.rc文件

service customized /system/bin/customizedserver
    class main
    user media
    group audio camera

我是用的Pandaboard
编译整个ROM,然后fastboot flashall

I/        ( 1080): ServiceManager: 0x40ce4f00
D/        ( 1080): CustomizedService instantiate
D/        ( 1080): CustomizedService created
E/ServiceManager(   94): add_service('test.customized',0x44) uid=xxxx - PERMISSION DENIED
D/        ( 1080): CustomizedService r = -1
D/        ( 1080): CustomizedService destroyed

或者你想从shell当中启动这个服务的进程也会出现类似权限不足的问题,追一下log就很快会发现问题

int do_add_service(struct binder_state *bs,
                   uint16_t *s, unsigned len,
                   void *ptr, unsigned uid, int allow_isolated)
{
    struct svcinfo *si;

    if (!ptr || (len == 0) || (len > 127))
        return -1;

    if (!svc_can_register(uid, s)) {
        ALOGE("add_service('%s',%p) uid=%d - PERMISSION DENIED\n",
             str8(s), ptr, uid);
        return -1;
    }

    si = find_svc(s, len);
 
    ......

    binder_acquire(bs, ptr);
    binder_link_to_death(bs, ptr, &si->death);
    return 0;
}
int svc_can_register(unsigned uid, uint16_t *name)
{
    unsigned n;
    
    if ((uid == 0) || (uid == AID_SYSTEM))
        return 1;

    for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++)
        if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name))
            return 1;

    return 0;
}

可以看出能注册service的用户的uid只能是0即ROOT或者是AID_SYSTEM,或者是allowed列表当中的服务,最简单的就是把所要添加的服务加到列表当中

{ AID_MEDIA, "test.customized" },

而服务allowed列表位于

/path/to/aosp/frameworks/base/cmds/servicemanager/service_manager.c

重新编译ROM,烧完之后启动看到customizedservice这个进程起来的话,就多半表明服务加好了

App端怎么调用?

写个Activity利用android.os.ServiceManager的getService获取到指定的服务,然后调用就可以了。
这里需要说明下的就是目前android.os.ServiceManager对于app来说是hide掉的,我们暂且可以不管,用个反射先用着。
至于为什么要hide掉,难道Android不想让我们自己定义系统级别的service来使用?还是说用其他的方式可以完成,目前我也不清楚!

目前结果就是通过getService能获取到我们的服务,并且成功调用,但是因为我返回的参数有String有int,而String没有获取出来,都知道Parcel的参数顺序是很重要的,可能是这个的原因,后面再来看看到底是什么原因,目前就是把customized service这一套先跑通。

P.S. 后来一次发现可能是我每次编译的so档案没有进到ROM当中去,所以烧到设备的so档案不是最新的,就没有把后面加的String获取出来(之前是两个int型数据)。
另外我还做了个实验,就是在service端通过ServiceManager的getService(…)方法再次访问当前service,这个就相当于在当前代码中通过一个指针或者引用访问的效果是一样的,这就是Binder的强大之处吧!此改动可以参见以下commit:

commit 1875ad41d5f8e505cb4c26599e4626944005f26b
Date: Tue Jun 25 12:10:59 2013 +0800

Symptom: test invoking getService in same process with service
Issue ID: NA
Root Cause: NA
Solution: NA

完整代码参见
https://github.com/guohai/and-customized-service.git

这里是Java端调用native端的服务,其实也可以native调用native的服务,这里暂时就不再去具体实现了,需要的话,会再来写。

无图无真相
add-customized-service-for-android
add-customized-service-for-android-1