现在的位置: 首页 > 综合 > 正文

Android多媒体架构分析

2013年07月04日 ⁄ 综合 ⁄ 共 27957字 ⁄ 字号 评论关闭

 

Android多媒体架构分析

Revision History

Date

Issue

Description

Author

<2/05/2010>

<0.5>

 

wylhistory

 

 

 

 

目录

1.    Abstract

2.    Introduction

3.    Android 多媒体架构

3.1      代码位置

3.2      MediaPlayer

3.3      jni层

3.4      MediaPlayer客户端

3.5      BnMediaPlayer

3.6      PVPlayer 层

3.7      PlayerDriver

3.8      引擎层PVPlayerEngine

3.9      PVPlayerDatapath层

3.10     节点层

3.11     MIO层

3.12     控制逻辑小结

4.    例子分析

4.1      New MediaPlayer的流程

4.2      setDataSource逻辑

4.3      引擎层prepare前的流程

4.3.1       PVPlayer的处理逻辑

4.3.2       Playerdriver的处理逻辑

4.3.3       引擎层的处理

4.3.4       Run_init的逻辑

4.3.5       Audio输出和video输出的设置

4.4      引擎层prepare的处理

4.4.1       PVP_ENGINE_STATE_INITIALIZED状态时的处理

4.4.2       PVP_ENGINE_STATE_TRACK_SELECTION_1_DONE逻辑

4.4.3       PVP_ENGINE_STATE_TRACK_SELECTION_2_DONE的逻辑

4.4.4       PVP_ENGINE_STATE_TRACK_SELECTION_3_DONE的处理

4.5      PVPlayerDatapath层的prepare相关处理逻辑

4.5.1       进入PREPARE_INIT状态以前的处理

4.5.2       PREPARE_INIT状态的逻辑

4.5.3       PREPARE_REQPORT状态的逻辑

4.5.4       PREPARE_CONNECT状态的逻辑

4.5.5       PREPARE_PREPARE的逻辑

4.6      Prepare的收官之战

4.7      Start流程的分析

4.7.1       Android本身架构对start的处理

4.7.2       PlayerDriver层的start流程

4.7.3       引擎层的start处理

4.8      数据 的流动

对于PV的架构,数据的传递分成两种模式,

4.8.1       Component的初始化

4.8.2       Tunnel模式的数据流动

4.8.3       非tunnel模式的数据流动

4.8.4       MIO的数据处理

5.    同步问题

6.    关于component的集成

6.1      接口库的加载时机

6.1.1       动态库的加载

6.1.2       omx_interface的实现

6.1.3       注册表的填充

7.    集成总结

8.    未分析

9.    reference

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1.               Abstract

主要是分析一下android的多媒体架构,以及和集成相关的东西.

2.               Introduction

Android的多媒体架构及其的复杂,代码量也是非常的大,甚至我认为是所有模块里面最复杂的一块,因为里面包含了音频,视频,时钟,同步等等;

说实话,文档很长,我都很难有勇气看两遍,所以错误在所难免,请多包涵!

 

3.               Android 多媒体架构

Android的多媒体架构简单划分可以分层两部分,opencore以及 opencore之上,如图:

                  图3.0.0 android多媒体架构

Opencore本身的架构又分成如下三个部分:

AL:application layer,这层,由android自己实现;

IL:integration layer,这层,marvel自己实现了一套编解码component;

DL:development layer;

如下图所示:

 

                    图3.0.1 openmax框架

 

总的来说,包括以下几个部分:

UI层:也就是用户操作的界面,比如播放暂停等,主要是mediaplayer.java;

JNI层:也就是连接java和c代码的地方,主要是android_media_mediaplayer.cpp;

Mediaplayer客户端:也就通过binder转发jni的命令到服务器端的地方,主要在mediaplayer.cpp里面;

服务器端:也就是接受客户端请求的地方,当然它依然只是一个管理的地方,不是真正干活的地方;

Framework和openmax的适配层:这里是连接android框架和openmax引擎的地方;

Openmax引擎:这里是引擎层,负责管理各个component的地方,并控制状态切换;

Component层:这里就是各个component了;

DL层:这里就是提供一些基本的操作原语的地方;

 

            下面分别介绍一下这几层,包括它们之间的交互,当然需要提一下相关的代码,最后以一个实际的例子来分析。

3.1               代码位置

以开源的Android为例MediaPlayer的代码主要在以下的目录中:

JAVA程序的路径:

packages/apps/Music/src/com/android/music/

 

JAVA本地调用部分(JNI):

frameworks/base/media/jni/android_media_MediaPlayer.cpp

这部分内容编译成为目标是libmedia_jni.so。

主要的头文件在以下的目录中:

frameworks/base/include/media/

这个目录是和libmedia.so库源文件的目录frameworks/base/media/libmedia/相对应的。主要的头文件有以下几个:

IMediaPlayerClient.h:定义了类class IMediaPlayerClient和类BnMediaPlayerClient,后者主要用来和服务通讯;

mediaplayer.h:定义了类 class MediaPlayer,继承于BnMediaPlayerClient,供jni层使用;

IMediaPlayer.h:这个是接口类,定义了class IMediaPlayer: public IInterface和class BnMediaPlayer: public BnInterface<IMediaPlayer>;

IMediaPlayerService.h :定义了接口class IMediaPlayerService: public IInterface,和class BnMediaPlayerService: public BnInterface<IMediaPlayerService>。

MediaPlayerInterface.h:

 

 

多媒体底层库在以下的目录中:

frameworks/base/media/libmedia/ 

这部分的内容被编译成库libmedia.so。

多媒体服务部分:

frameworks/base/media/libmediaplayerservice/

文件为mediaplayerservice.h和mediaplayerservice.cpp

这部分内容被编译成库libmediaplayerservice.so。

基于OpenCore的多媒体播放器部分 

external/opencore/

这部分内容被编译成库libopencoreplayer.so。

 

 从程序规模上来看,libopencoreplayer.so是主要的实现部分,而其他的库基本上都是在其上建立的封装和为建立进程间通讯的机制,当然复杂的部分是编解码器,我们一般看不到。

 

 

3.2               MediaPlayer

这是一个比较上层的类,是给更上层或者说UI程序使用的java类;它的存在就是为了完全独立底层的实现,比如它的一个典型的用法就是这样:

MediaPlayer mp=new MediaPlayer();

mp.setDataSource(PATH_TO_FILE);

mp.prepare();

mp.start();

它所交互的层就是JNI层,也就是说它真正做的事都是通过JNI来做的,这几句话做的事情后面会详细介绍,这里就是有一个直观的了解就行了。

3.3               jni层

这里有一个表,如下:

static JNINativeMethod gMethods[] = {

    {"setDataSource",       "(Ljava/lang/String;)V",            (void *)android_media_MediaPlayer_setDataSource},

    {"setDataSource",       "(Ljava/io/FileDescriptor;JJ)V",    (void *)android_media_MediaPlayer_setDataSourceFD},

    {"_setVideoSurface",    "()V",                              (void *)android_media_MediaPlayer_setVideoSurface},

    {"prepare",             "()V",                              (void *)android_media_MediaPlayer_prepare},

    {"prepareAsync",        "()V",                              (void *)android_media_MediaPlayer_prepareAsync},

    {"_start",              "()V",                              (void *)android_media_MediaPlayer_start},

    {"_stop",               "()V",                              (void *)android_media_MediaPlayer_stop},

    {"getVideoWidth",       "()I",                              (void *)android_media_MediaPlayer_getVideoWidth},

    {"getVideoHeight",      "()I",                              (void *)android_media_MediaPlayer_getVideoHeight},

    {"seekTo",              "(I)V",                             (void *)android_media_MediaPlayer_seekTo},

    {"_pause",              "()V",                              (void *)android_media_MediaPlayer_pause},

    {"isPlaying",           "()Z",                              (void *)android_media_MediaPlayer_isPlaying},

    {"getCurrentPosition",  "()I",                              (void *)android_media_MediaPlayer_getCurrentPosition},

    {"getDuration",         "()I",                              (void *)android_media_MediaPlayer_getDuration},

    {"_release",            "()V",                              (void *)android_media_MediaPlayer_release},

    {"_reset",              "()V",                              (void *)android_media_MediaPlayer_reset},

    {"setAudioStreamType",  "(I)V",                             (void *)android_media_MediaPlayer_setAudioStreamType},

    {"setLooping",          "(Z)V",                             (void *)android_media_MediaPlayer_setLooping},

    {"isLooping",           "()Z",                              (void *)android_media_MediaPlayer_isLooping},

    {"setVolume",           "(FF)V",                            (void *)android_media_MediaPlayer_setVolume},

    {"getFrameAt",          "(I)Landroid/graphics/Bitmap;",     (void *)android_media_MediaPlayer_getFrameAt},

    {"native_invoke",       "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke},

    {"native_setMetadataFilter", "(Landroid/os/Parcel;)I",      (void *)android_media_MediaPlayer_setMetadataFilter},

    {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_getMetadata},

    {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},

    {"native_setup",        "(Ljava/lang/Object;)V",            (void *)android_media_MediaPlayer_native_setup},

    {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},

    {"snoop",               "([SI)I",                           (void *)android_media_MediaPlayer_snoop},

};

   可以看见,这里面有对先前调用的setDataSource,prepare,start的本地实现,比如:

static void

android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz)

{

    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);

    if (mp == NULL ) {

        jniThrowException(env, "java/lang/IllegalStateException", NULL);

        return;

    }

    setVideoSurface(mp, env, thiz);

    process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." );

}

 

这里也只是跑马观花的走一圈,大约就是先取得已经在new的时候建立的MediaPlayer,然后mp->setVideoSurface,然后开始调用mp->prepare,prepare里面做的工作是最多的,非常的繁琐,后面会讲,从应用程序的角度来说,prepare以后,就可以通过start来启动了;

3.4               MediaPlayer客户端

如果理解binder的机制,这个就很好理解了,这个就是MediaplayerService在客户端的代理了,它的创建如下所示:

const sp<IMediaPlayerService>& MediaPlayer::getMediaPlayerService()

{

    Mutex::Autolock _l(sServiceLock);

    if (sMediaPlayerService.get() == 0) {

        sp<IServiceManager> sm = defaultServiceManager();

        sp<IBinder> binder;

        do {

            binder = sm->getService(String16("media.player"));

            if (binder != 0)

                break;

            LOGW("MediaPlayerService not published, waiting...");

            usleep(500000); // 0.5 s

        } while(true);

        if (sDeathNotifier == NULL) {

            sDeathNotifier = new DeathNotifier();

        }

        binder->linkToDeath(sDeathNotifier);

        sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);

    }

    LOGE_IF(sMediaPlayerService==0, "no MediaPlayerService!?");

    return sMediaPlayerService;

}

通过servicemanager利用getService就可以返回一个BpMediaPlayerService,然后通过它的create函数也就是service->create(getpid(), this, url)就创建了一个BpMediaPlayer,这两个结构本质上就是BnMediaPlayerService和BnMediaPlayer的代理,通过它就可以访问后面这两个服务器端的方法了,比如:

mp->prepare()

最后就会调用mPlayer->prepareAsync()

它最后会调用到BnMediaPlayer里面的prepareAsync了;

 

3.5               BnMediaPlayer

 mPlayer->prepareAsync的实现如下(注意这里的Client原型为:

class Client : public BnMediaPlayer):

status_t MediaPlayerService::Client::prepareAsync()

{

    LOGV("[%d] prepareAsync", mConnId);

    sp<MediaPlayerBase> p = getPlayer();

    if (p == 0) return UNKNOWN_ERROR;

    status_t ret = p->prepareAsync();

#if CALLBACK_ANTAGONIZER

    LOGD("start Antagonizer");

    if (ret == NO_ERROR) mAntagonizer->start();

#endif

    return ret;

}

可以看到中里面的prepareAsync还是通过一个p->prepareAsync来实现的,而p是来自于哪里呢?

如下:

可见p是在setDataSource的时候,通过new创建出来的一个PVPlayer实例;

这样控制就到了PVPlayer层了,继续往下看:

 

 

3.6               PVPlayer 层

我们还是先不看它的初始化等了,因为内容是在是太多了,还是看看上面的prepareAsync

status_t PVPlayer::prepareAsync()

{

    LOGV("prepareAsync");

    status_t ret = OK;

 

    if (!mIsDataSourceSet) {  // If data source has NOT been set.

        // Set our data source as cached in setDataSource() above.

        LOGV("  data source = %s", mDataSourcePath);

        ret = mPlayerDriver->enqueueCommand(new PlayerSetDataSource(mDataSourcePath,run_init,this));

        mIsDataSourceSet = true;

    } else {  // If data source has been already set.

        // No need to run a sequence of commands.

        // The only code needed to run is PLAYER_PREPARE.

        ret = mPlayerDriver->enqueueCommand(new PlayerPrepare(do_nothing, NULL));

    }

 

    return ret;

}

 

这个PVPlayer::prepareAsync基本上没干啥有用的,只是把一条命令加入到了一个队列,设置标志位,就返回了,这也就是为什么名字上有async的原因了;

OK,前面的所有的逻辑基本上都是二传手,下面着重分析的是PVPlayer以及下面几层;

 

3.7               PlayerDriver

PVPlayer加这层基本上可以看做是android的多媒体架构和opencore之间的桥梁;

 

它的类结构如下:

它还有许多许多的函数和成员没列出来,我们将要讨论相关的包括这几个:

A)      PVPlayer也就是上面已经提到过的,Android 媒体播放器的代言人;

B)      而PVPlayerInterface则是opencore的引擎层的代理人;

C)      红色的两个成员是和我们将要讨论的视频的输出有关的,后面会讲到;

D)      至于其它的它的父类,为了不打断我们的思路,就留到后面讲,简单的说:PVInformationalEventObserver的作用就是给引擎层访问的PlayerDriver提供接口,用来处理引擎层或以下上传的事件;而OsclActiveObject简单说就是一个执行上下文,它类似一个Timer,到期就会执行它的Run函数,如果没人触发它将一直不执行,比如PVPlayer发送到队列里面的处理就是在Run函数里面处理的;而PVCommandStatusObserver也和PVInformationalEventObserver类似,是由引擎层来调用的接口,以方便PlayerDriver传递的命令的执行状态的跟踪;PVErrorEventObserver类似;

 

PLayerDriver的逻辑架构很简单,就是将PVPlayer的命令放入队列,然后在run函数里面一个个取出来处理,凡是以”handle”开头的函数都是实际的处理函数,而不带handle的都是加入到队列的函数,而带handle的函数的处理一般都是这样的,比如先前的prepareAsync命令的处理函数:

void PlayerDriver::handlePrepare(PlayerPrepare* command)

{

    //Keep alive is sent during the play to prevent the firewall from closing ports while

    //streaming long clip

    PvmiKvp iKVPSetAsync;

    OSCL_StackString<64> iKeyStringSetAsync;

    PvmiKvp *iErrorKVP = NULL;

    int error=0;

    iKeyStringSetAsync=_STRLIT_CHAR("x-pvmf/net/keep-alive-during-play;valtype=bool");

    iKVPSetAsync.key=iKeyStringSetAsync.get_str();

    iKVPSetAsync.value.bool_value=true;

    iErrorKVP=NULL;

    OSCL_TRY(error, mPlayerCapConfig->setParametersSync(NULL, &iKVPSetAsync, 1, iErrorKVP));

    OSCL_TRY(error, mPlayer->Prepare(command));

    OSCL_FIRST_CATCH_ANY(error, commandFailed(command));

 

    char value[PROPERTY_VALUE_MAX] = {"0"};

    property_get("ro.com.android.disable_rtsp_nat", value, "0");

    LOGV("disable natpkt - %s",value);

    if (1 == atoi(value))

    {

        //disable firewall packet

        iKeyStringSetAsync=_STRLIT_CHAR("x-pvmf/net/disable-firewall-packets;valtype=bool");

        iKVPSetAsync.key=iKeyStringSetAsync.get_str();

        iKVPSetAsync.value.bool_value = 1; //1 - disable

        iErrorKVP=NULL;

        OSCL_TRY(error,mPlayerCapConfig->setParametersSync(NULL,&iKVPSetAsync,1,iErrorKVP));

    }

}

 

看到了吗,实际的处理是通过mPlayerCapConfig->setParametersSync 和mPlayer->Prepare来完成的,而实际上对于opencore这个mPlayerCapConfig和mPlayer是同一个指针;

3.8               引擎层PVPlayerEngine

对于命令的实际处理,比如prepare等,最后都落在了PVPlayerEngine这里了,这里是opencore的核心,它是基于状态机的,逻辑处理有点像PlayerDriver,也是先把命令放到队列里面,然后在run函数里面一个个取出来执行的,不过非常繁琐,就这一个文件就有一万七千行!!!还是先看看它的组成结构吧:

class PVPlayerEngine

        : public OsclTimerObject

        , public PVPlayerInterface

        , public PvmiCapabilityAndConfigBase

        , public PVMFNodeCmdStatusObserver

        , public PVMFNodeInfoEventObserver

        , public PVMFNodeErrorEventObserver

        , public PVPlayerDatapathObserver

        , public OsclTimerObserver

        , public PVPlayerLicenseAcquisitionInterface

        , public PVPlayerRecognizerRegistryObserver

        , public PVPlayerWatchdogTimerObserver

        , public PVPlayerTrackSelectionInterface

        , public PVMFMediaClockNotificationsObs

        , public ThreadSafeQueueObserver

        , public PVMFCPMStatusObserver

估计看到这一堆东西,你就再没有胃口看下去了,我也是;

或许另外一个简单一点的图可以让你喘口气:

它的创建流程如下:

我最多能够简单介绍一下:它的特点就是凡是以”Do”开始的就是实际的处理命令的地方,而不带Do的就只是把命令加入到队列的地方,比如:

PVCommandId PVPlayerEngine::Prepare(const OsclAny* aContextData)

{

    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVPlayerEngine::Prepare()"));

    return AddCommandToQueue(PVP_ENGINE_COMMAND_PREPARE, (OsclAny*)aContextData);

}

这个不带“Do”的就只是通过AddCommandToQueue加入到队列,而

PVMFStatus PVPlayerEngine::DoPrepare(PVPlayerEngineCommand& aCmd)

{

   …

}

就是实际的处理的地方,这个函数很大,实际上绝大多数工作都是在这里做或者说发起的。

其中Oscl_Vector<PVPlayerEngineCommand, OsclMemAllocator> iCurrentCmd;是描述当前正在处理的命令,而OsclPriorityQueue<PVPlayerEngineCommand, OsclMemAllocator, Oscl_Vector<PVPlayerEngineCommand, OsclMemAllocator>, PVPlayerEngineCommandCompareLess> iPendingCmds; // Vector to hold the command that has been requested是描述放入到队列里面的命令,引擎的核心就是run函数,在那里会根据状态以及命令的类型一个个取出来然后处理;

也许可以看看它的状态转换图,就大约明白它都干了些什么:

画得很清楚了,也就是正常情况下从IDLE——>INITIALIZED——>PREPARED——>STARTED,当然实际的情况会很复杂,比如对于prepared,里面又细分为好几个状态,后面会讲到;

3.9               PVPlayerDatapath层

什么时候会走到这里?

在引擎层的命令处理中,有一部分就是构造这个PVPlayerDatapath,而另外一部分就是调用这个PVPlayerDatapath来处理相关的命令,比如prepare,start,pause等;也来看看它的结构:

这里面需要说明的是PVMFNodeInterface,它其实就是存放的实际的node,比如iSourceNode,iDecNode,iSinkNode,而PVMFPortInterface就是存放的这些Node所拥有的这些Port,而这些port才是实际负责传递数据的对象,比如:

PVMFPortInterface* iSourceOutPort;

        PVMFPortInterface* iDecInPort;

        PVMFPortInterface* iDecOutPort;

        PVMFPortInterface* iSinkInPort;

它们的连接方式像这样:

而ISourceOutPort就有可能是来自于一个文件构成的node,中间两个port是由一个解码器的node来提供的,最后是一个sink port它就有可能是视频输出的地方了;

3.10            节点层

这里讨论的是 PVMediaOutputNode,因为我们比较关注的是它和MIO的集成,先看看它的组成:

它的处理逻辑和前面讨论的playerdriver一样,都是有一个自己的run函数里面处理datapath层(PVPlayerDatapath)传下来的命令,比如:

OSCL_EXPORT_REF PVMFCommandId PVMediaOutputNode::Prepare(PVMFSessionId s, const OsclAny* aContext)

{

    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE,

                    (0, "PVMediaOutputNode::Prepare() called"));

    PVMediaOutputNodeCmd cmd;

    cmd.PVMediaOutputNodeCmdBase::Construct(s, PVMF_GENERIC_NODE_PREPARE, aContext);

    return QueueCommandL(cmd);

}

这个就是只放到队列里面,真正的处理都是在以”Do”开头的函数里面,比如:

PVMFStatus PVMediaOutputNode::DoPrepare(PVMediaOutputNodeCmd& aCmd)

{

    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE,

                    (0, "PVMediaOutputNode::DoPrepare"));

 

    if (iInterfaceState != EPVMFNodeInitialized)

        return PVMFErrInvalidState;

 

    return SendMioRequest(aCmd, EInit);

 

}

可以看到它的实现又通过SendMioRequest由iMIOControl来完成具体的工作:

PVMFStatus PVMediaOutputNode::SendMioRequest(PVMediaOutputNodeCmd& aCmd, EMioRequest aRequest)

{

case EInit:

        {

            PvmiMediaTransfer* mediaTransfer = NULL;

            if (iInPortVector.size() > 0)

            {

                mediaTransfer = iInPortVector[0]->getMediaTransfer();

            }

            if (mediaTransfer != NULL)

            {

                OSCL_TRY(err, iMediaIOCmdId = iMIOControl->Init(););

            }

            if ((err != OsclErrNone))

            {

                PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_ERR,

                                (0, "PVMediaOutputNode::SendMioRequest: Error - iMIOControl->Init failed"));

                aCmd.iEventCode = PVMFMoutNodeErr_MediaIOInit;

                status = PVMFFailure;

            }

        }

        break;

}

总之这个输出节点的处理又是通过这个iMIOControl的init来完成具体的准备工作;

它的整体工作逻辑,也就是数据传递的流程如下(后面会详细介绍):

上面的什么“Framework Component”也就是我说的Node了,而下面的那些”Source Component”等就是我说说的解码的或者源的component了,而对于所谓的tunnel模式,Non-tunnel模式也留到后面再说吧;

 

3.11            MIO层

按照marvel的处理逻辑,最后的数据输出都是经过MIO出来的,所以需要看看它的结构:

android默认实现了PvmiMIOControl,PvmiMediaTransfer和PvmiCapabilityAndConfig以及OsclTImerObject接口,这样就可以在一个类里面做配置传输等工作了;其中的iPeer也就是和它传递数据的对端,而mSurface也就是代表物理的层,当然,对于marvel的实现,这个成员仅仅是用来判断是否在顶层,而数据的传递是直接通过打开fb2来发送的,后面会讲;

现在从整体上看看它和上层应用的交互:

基本上这些逻辑前面都已经有提到了,至于具体的函数流程图后面再讲,这里先看一个数据传输起始图:

3.12            控制逻辑小结

到这里我们先整理一下控制逻辑的传递过程:

 

控制流到了Node层,对于不同的目的的Node处理不一样,比如上面已经讨论的output 的node它就需要对MIO做初始化等,而对于decode的node,就需要对里面所拥有的decode的component进行初始化等;

 

4.               例子分析

我们回到先前的例子:

MediaPlayer mp=new MediaPlayer();

mp.setDataSource(PATH_TO_FILE);

mp.prepare();

mp.start();

我们来一步步看看,里面是怎么实现的!

4.1               New MediaPlayer的流程

如下:

本质上就是构建了一个MediaPlayer,并设置了监听者,用于通知上层关于mp的事件;

注意这时候并没有和MediaPlayerService端建立连接,也就是说这时候仅仅是MediaPlayer还不是IMediaPlayer*;

 

4.2               setDataSource逻辑

它主要完成的功能是:

A) 客户端的mPlayer的创建;

B)  对于服务器端的mPlayer (=new PVPlayer())的创建;

C)  mAudioOutput = new AudioOutput(); 这个用于audio输出,不过我不打算讨论这块。

D) mDataSourcePath = strdup(url);这里保存源地址;

逻辑图如下:

 

 

这里的真正工作基本上都是在服务器端实现的,主要干了一下几件事:

A) 创建PVPlayer,它的作用前面已经说了,是连接Android的多媒体架构和pv架构的桥梁;

B) 创建音频输出层,将来音频输出将从这里出来;

C) 保存源地址,供prepare的时候真正使用;

D) 更新状态为INITIALIZED;

 

4.3               引擎层prepare前的流程

这个逻辑及其复杂,几乎所有的为执行prepare的准备工作都是在这里做的,需要分成

A) PVPlayer及以上;

B)  PlayerDriver的处理;

C)  PVPlayerEngine的处理;

D) PVPlayerDatapath的处理;

下面分别讨论:

4.3.1          PVPlayer的处理逻辑

先看看逻辑是怎么到PVPlayer的:

上层的prepare到了这里就变成了prepareAsync了,前面有提到过,这里就是把命令加入到队列,然后从run函数里面取出来处理,先看看命令的加入,如下图,主要做了两件事:

A) 构造并加入PLAYER_SET_DATA_SOURCE命令到队列里面;

B)  设置回调函数;

这两步又会触发一些列的动作;

4.3.2          Playerdriver的处理逻辑

可以看到先前的setDataSource命令在这里会真正实现,而run_init的执行要等到命令执行完毕才会被执行,它的执行逻辑后面会讲,先看看PLAYER_SET_DATA_SOURCE命令的处理:

可以看到PlayerDriver的处理逻辑就是先从队列里面取出命令,然后根据命令类型,比如这次是PLAYER_SET_DATA_SOURCE就调用handleSetDataSource来处理,在这里再根据source的类型初始化一下成员,比如source的格式等,最后调用引擎层的AddDataSource来处理;

4.3.3          引擎层的处理

引擎层的处理就是把它加入到队列里面,如下:

在AddCommandToQueue里面会调用RunIfNotReady函数,这个函数的调用逻辑很复杂,不过你可以简单的理解为它会触发这个active object的run函数,于是我们看看引擎层的run函数如何处理这个命令,看看它的注释吧:

/* Engine AO will execute commands in the following sequence:

     * 1) If Engine state is Resetting, which will happen when Engine does ErrorHandling,

     *    or is processing Reset or CancelAllCommands issued by the app, engine will NOT execute

     *    any other command during this state.

     * 2) If Engine is not in Resetting state, then it will process commands in the following order,

     *    which ever is true:

     *      (i) If Engine needs to do Error handling because of some error from Source Node or Datapath,

     *          either start error handling or complete it.

     *     (ii) If Engine has Reset or CancelAllCommands in CurrentCommandQueue,

     *          Engine will do CommandComplete for the CurrentCommand.

     *    (iii) If Engine has Prepare in CurrentCommandQueue, Engine will call DoPrepare again

     *          as a part of track selection logic.

     *     (iv) If Engine has CancelAllCommands or CancelAcquireLicense in Pending CommandQueue,

     *          Engine will start Cancel commands.

     *      (v) Go for Rollover if in Init State and Roll-over is ongoing.

     *     (vi) Process which ever command is pushed in Pending queue.

     * Engine will process any one of the command as listed above in the same order.

     * Every time engine AO is scheduled, engine will go through these steps.

     */

简单的说,它需要先考虑错误处理,然后考虑一些reset,或者取消等命令,再次考虑是不是在Prepare状态,如果是就会再次调用DoPrepare,这在后面我们会看到对于prepare的复杂的处理!!!然后考虑是不是正处于取消命令等的处理过程中,然后考虑需不需要回滚,最后才是处理在iPendingCmds里面的命令,现在对于我们正在讨论的情况,就要到iPendingCmds里面去处理了,先取出命令判断类型,如果是PVP_ENGINE_COMMAND_ADD_DATA_SOURCE类型就调用DoAddDataSource来做实际的事情,它的调用逻辑如下:

在DoAddDataSource,分成格式是否被识别,做不同的处理,比如对于一个普通的视频,这时候它的格式是没有被识别的所以需要走的路线是:

DoQuerySourceFormatType(aCmd.GetCmdId(), aCmd.GetContext());

也就是先调用识别器来识别源文件的格式:

iPlayerRecognizerRegistry.QueryFormatType(…)

至于它是如何识别的,就不分析了,只需要知道的是,如果识别以后,会通过:

iObserver->RecognizeCompleted(iSourceFormatType, iCmdContext);来通知上层,比如对于引擎层,

它的RecognizeCompleted函数将会被调用,调用逻辑如下:

先保存识别出的URL格式,然后调用DoSetupSourceNode来处理,

这个函数主要的作用就是:

1,   根据源文件格式查找出对应的UUID;

2,   根据UUID创建source node;

3,   连接source node;

4,   查询PVP_CMD_SourceNodeQueryInitIF接口;

如下:

不同的Node对于ThreadLogon和connect的实现都不一样,有的node基本上什么事都没干,这两个函数的目的,就有点像session,也就是说如果事情需要在一个会话里面初始化的,那么就需要把事情在那里做,比如特定于session的空间的分配等(主要目的是为了多线程的并发处理);

这里需要重点说一下的是第四步,这里通过查询PVP_CMD_SourceNodeQueryInitIF接口后会触发HandleSourceNodeQueryInitIF的执行,它的逻辑如下:

 如果成功的话,iSourceNodePVInterfaceTrackSel会被初始化,这个变量非常重要,因为它负责文件的解析,也就是track的建立,详细信息,后面会讲;

OK,我们现在假定我们这些事都已经成功的完成了,那么先前设好的run_init函数将会被执行了,也许你会说,它的回调逻辑也就是执行逻辑是怎么来的,OK,好吧,我们来看看:

每当一个node执行完一个命令后,将会触发它的CommandComplete函数,在那里,它会调用node的观察者的NodeCommandCompleted,在这里,我们的观察者就是PVPlaerEngine,于是它的NodeCommandCompleted函数将会被执行,它当然还得回到PlayerDriver层才行,它的逻辑如下:

到这里,我们先前设定的command的complete函数终于得以执行,也就是run_init函数被执行了,现在看看它的逻辑:

4.3.4          Run_init的逻辑

这个代码很短,如下:

void PVPlayer::run_init(status_t s, void *cookie, bool cancelled)

{

    LOGV("run_init s=%d, cancelled=%d", s, cancelled);

    if (s == NO_ERROR && !cancelled) {

        PVPlayer *p = (PVPlayer*)cookie;

        p->mPlayerDriver->enqueueCommand(new PlayerInit(run_set_video_surface, cookie));

    }

}

PlayerInit(media_completion_f cbf, void* cookie) :

            PlayerCommand(PLAYER_INIT, cbf, cookie) {}

 

      和其它命令的处理逻辑一样,就是加入到PlayerDriver的命令队列里面,等待被处理;

像run_init本身的执行逻辑一样,这里的run_set_video_surface,也就是命令本身设置的回调函数,它的调用逻辑前面已经讲过了,它的执行逻辑后面再讲,这里先看看对于PLAYER_INIT的命令,playerdriver是怎么处理的,如下:

 

 

和上面的setDataSource逻辑一样,最后这些命令的处理都得到引擎层去实现,对于init命令,最后会调用到sourceNode的init命令来处理(后面会讲到decode以及sinknode的初始化);这个命令处理后会先触发引擎层的回调函数,逻辑如下:

也就是说引擎的状态现在已经变成了PVP_ENGINE_STATE_INITIALIZED!!!

当这个命令的执行完毕以后,我们先前设置好的playerdriver层的命令回调函数run_set_video_surface将会被触发了,在讨论它的逻辑之前,需要先看看这段代码,前面已经贴出来过:

static void

android_media_MediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)

{

    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);

    if (mp == NULL ) {

        jniThrowException(env, "java/lang/IllegalStateException", NULL);

        return;

    }

    jobject surface = env->GetObjectField(thiz, fields.surface);

    if (surface != NULL) {

        const sp<Surface> native_surface = get_surface(env, surface);

        LOGV("prepareAsync: surface=%p (id=%d)",

             native_surface.get(), native_surface->ID());

        mp->setVideoSurface(native_surface);

    }

    process_media_player_call( env, thiz, mp->prepareAsync(), "java/io/IOException", "Prepare Async failed." );

}

 

也就是说在JNI层调用prepareAsync的时候会先设置surface,对于PVPlayer,这个逻辑很简单,只是保存一下:

status_t PVPlayer::setVideoSurface(const sp<ISurface>& surface)

{

    LOGV("setVideoSurface(%p)", surface.get());

    mSurface = surface;

    return OK;

}

OK,现在可以看看mp->setVideoSurface(native_surface)了:

4.3.5          Audio输出和video输出的设置

先看看这个简单而丑陋的图:

 

      对于命令的加入和取出以及回调函数的逻辑,前面已经说过,以后我都直接讨论函数本身了,这里需要分成至少三部分:

A) PLAYER_SET_VIDEO_SURFACE的命令的处理;

B)  对于上面的命令执行完毕后的回调函数run_set_audio_output,即PLAYER_SET_AUDIO_SINK命令的处理;

C)  对于PLAYER_SET_AUDIO_SINK命令处理完毕后的回调函数run_prepare;

 

4.3.5.1     PLAYER_SET_VIDEO_SURFACE处理

它的处理逻辑如下:

a) 先尝试通过动态库加载的形式来创建MIO,我看omap的芯片很多是通过这种方式来实现MIO的;

b)       如果没有实现硬件特定的MIO,就创建一个Android框架提供的通用的MIO;

c)       为MIO设置输出surface,以及传递一个PVPlayer,用于接收MIO传递上来的事件(目前未看见marvel的相关实现);

d)       mVideoOutputMIO = mio;保存此MIO;

e)       创建mVideoNode,这个是用来和MIO交互的component;

f)        创建mVideoSink并设置sink node,即SetDataSinkNode(mVideoNode);

g)       设置视频输出格式为PVMF_MIME_YUV420;

h)       mPlayer->AddDataSink(*mVideoSink, command),让引擎层添加data sink;

 

这里需要特别说明的是marvel用的是通用的MIO,也就是AndroidSurfaceOutput,然后在通过set把fb2的surface设置进去(我的猜想),在输出component也就是mVideoNode里面利用这个fb2把数据写出去,而omap是通过一个扩展的库libopencorehw.so来扩展实现的;

另外,这里的mVideoNode和mVideoSink有点混淆,其实可以这么理解,mVideoSink是OPENCORE层需要的node,而mVideoNode是所谓的component,它是和解码器一个层次的,node在更上面一层;

下面来看看引擎层的AddDataSink:

它先是加到命令队列,然后从命令队列取出来调用DoAddDataSink处理,简略示意如下:

 

PVPlayerDataSink* datasink = (PVPlayerDataSink*)(aCmd.GetParam(0).pOsclAny_value);

   PVPlayerEngineDatapath newdatapath;

   newdatapath.iDataSink = datasink;

 

    // Add a new engine datapath to the list for the data sink

    iDatapathList.push_back(newdatapath);

 

    EngineCommandCompleted(aCmd.GetCmdId(), aCmd.GetContext(), PVMFSuccess);

 

         简单的说就是加入到引擎所拥有的datapathlist里面,然后调用回调函数;

4.3.5.2     run_set_audio_output的处理逻辑

当PLAYER_SET_VIDEO_SURFACE处理完后,对应的command的回调函数将会被触发了,代码如下:

void PVPlayer::run_set_audio_output(status_t s, void *cookie, bool cancelled)

{

    LOGV("run_set_audio_output s=%d, cancelled=%d", s, cancelled);

    if (s == NO_ERROR && !cancelled) {

        PVPlayer *p = (PVPlayer*)cookie;

        p->mPlayerDriver->enqueueCommand(new PlayerSetAudioSink(p->mAudioSink, run_prepare, cookie));

    }

}

PlayerCommand(PLAYER_SET_AUDIO_SINK, cbf, cookie), mAudioSink(audioSink) {}

它的处理逻辑和surface的逻辑基本差不多,如下:

void PlayerDriver::handleSetAudioSink(PlayerSetAudioSink* command)

{

    int error = 0;

    if (command->audioSink()->realtime()) {

        LOGV("Create realtime output");

        mAudioOutputMIO = new AndroidAudioOutput();

    } else {

        LOGV("Create stream output");

        mAudioOutputMIO = new AndroidAudioStream();

    }

    mAudioOutputMIO->setAudioSink(command->audioSink());

 

    mAudioNode = PVMediaOutputNodeFactory::CreateMediaOutputNode(mAudioOutputMIO);

    mAudioSink = new PVPlayerDataSinkPVMFNode;

 

    ((PVPlayerDataSinkPVMFNode *)mAudioSink)->SetDataSinkNode(mAudioNode);

    ((PVPlayerDataSinkPVMFNode *)mAudioSink)->SetDataSinkFormatType((char*)PVMF_MIME_PCM16);

 

    OSCL_TRY(error, mPlayer->AddDataSink(*mAudioSink, command));

    OSCL_FIRST_CATCH_ANY(error, commandFailed(command));

}

如果您要问这个audioSink的来源,那就要追溯到mediaplayerservice::Clinet里面的setDataSource了,

相关代码如下:

sp<MediaPlayerBase> p = createPlayer(playerType);

        if (p == NULL) return NO_INIT;

if (!p->hardwareOutput()) {

            mAudioOutput = new AudioOutput();

            static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput);

  }

这个P就是返回给客户端的那个player,所以audiosink来自于此,其它的逻辑和视频差不多,也是创建了一个MIO,并且调用mPlayer的AddDataSink加入到datapathlist里面去了;

4.3.5.3     run_prepare的处理

prepare的处理终于开始了,它本身只是加入到队列里面去,然后调用handlePrepare,在里面基本上就是先设置参数,然后调用:

mPlayer->Prepare(command)来处理;

这个mPlayer就是我们所说的引擎层的player了;

 

4.4               引擎层prepare的处理

前面已经提到run_prepare最后都是通过mPlayer->Prepare来实现的,下面开始看看它的入口逻辑:

 

在进入DoPrepare之前,需要先说明一下,

引擎层对于prepare的处理分成四个阶段,如下:

A) 引擎处于PVP_ENGINE_STATE_INITIALIZED,这是第一次进入DoPrepare的情况;

B)  引擎处于PVP_ENGINE_STATE_TRACK_SELECTION_1_DONE;

C)  引擎处于PVP_ENGINE_STATE_TRACK_SELECTION_2_DONE;

D) 引擎处于PVP_ENGINE_STATE_TRACK_SELECTION_3_DONE;

下面分别对这几种情况进行讨论:

 

4.4.1            PVP_ENGINE_STATE_INITIALIZED状态时的处理

 

4.4.1.1     引擎层处理

 先看看这个逻辑图:

处于第一个状态时做的事情包括:

1,   根据iSourceNodeTrackSelIF接口对象取得源文件的track信息(后面会讲);

2,   sinknode->threadLogon;sinknode->Connect;就是做一些与线程相关的初始化;

3,   查询这个sink node的配置接口;

4,   查询这个sink node的同步控制接口;

这两个命令都会通过IssueQueryInterface的方式来发送给节点层,然后当命令返回后在引擎层的NodeCommandCompleted函数里面会根据当前的状态(PVP_ENGINE_STATE_PREPARING)然后根据命令的类型做处理,比如刚才这个两个命令的逻辑如下:

case PVP_CMD_SinkNodeQuerySyncCtrlIF:

 case PVP_CMD_SinkNodeQueryCapConfigIF:

          HandleSinkNodeQueryInterfaceMandatory(*nodecontext, aResponse);

           break;

而在函数HandleSinkNodeQueryInterfaceMandatory里面会保存这个配置接口和同步接口,相关代码如下:

OSCL_EXPORT_REF bool PVMediaOutputNode::queryInterface(const PVUuid& uuid, PVInterface*& iface)

{

    if (uuid == PvmfNodesSyncControlUuid)

    {

        PvmfNodesSyncControlInterface* myInterface = OSCL_STATIC_CAST(PvmfNodesSyncControlInterface*, this);

        iface = OSCL_STATIC_CAST(PVInterface*, myInterface);

        ++iExtensionRefCount;

    }

    else if (uuid == PVMI_CAPABILITY_AND_CONFIG_PVUUID)

    {

        PvmiCapabilityAndConfig* myInterface = OSCL_STATIC_CAST(PvmiCapabilityAndConfig*, this);

        iface = OSCL_STATIC_CAST(PVInterface*, myInterface);

        ++iExtensionRefCount;

    }

    else

    {

        iface = NULL;

        return false;

    }

 

    return true;

}

所以,我们都提供了相关接口,并且就是PVMediaOutputNode本身(this);

取得这个两个接口后,HandleSinkNodeQueryInterfaceMandatory先保存:

aNodeContext.iEngineDatapath->iSinkNodeCapConfigIF = (PvmiCapabilityAndConfig*)aNodeContext.iEngineDatapath->iSinkNodePVInterfaceCapConfig;

函数最后会调用DoSinkNodeInit来对SinkNode进行初始化;

DoSinkNodeInit——>IssueSinkNodeInit——>aDatapath->iSinkNode->Init

最后就是这个SinkNode的初始化了(这里已经看到了sink的初始化),下面来看看node本身的处理吧;

4.4.1.2     Node层及以下的处理

简单的看一下示意代码:

OSCL_EXPORT_REF PVMFCommandId PVMediaOutputNode::Init(PVMFSessionId s, const OsclAny* aContext)

{

    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE,

                    (0, "PVMediaOutputNode::Init() called"));

    PVMediaOutputNodeCmd cmd;

    cmd.PVMediaOutputNodeCmdBase::Construct(s, PVMF_GENERIC_NODE_INIT, aContext);

    return QueueCommandL(cmd);

}

从这里可以看出,Node层的处理几乎和它的父亲或者祖父如出一辙,就是一个命令队列,然后把命令一个个加入进去,在处理函数run里面调用ProcessCommand来处理,另外调用CommandComplete来通知上层,命令已经执行完毕,如下:

void PVMediaOutputNode::Run()

{

    //Process async node commands.

    if (!iInputCommands.empty())

    {

        ProcessCommand();

    }

 

    //Check for completion of a flush command...

    if (iCurrentCommand.size() > 0

            && iCurrentCommand.front().iCmd == PVMF_GENERIC_NODE_FLUSH

            && PortQueuesEmpty())

    {

        //Flush is complete.

        CommandComplete(iCurrentCommand, iCurrentCommand.front(), PVMFSuccess);

    }

}

对于init命令来说,处理逻辑如下:

  case PVMF_GENERIC_NODE_INIT:

                    cmdstatus = DoInit(aCmd);

                    break;

DoInit——> SendMioRequest(aCmd, EQueryClockExtension);

这里需要注意的是后面的那个参数,并不是EInit,而是EQueryClockExtension,

所以处理的逻辑就是查询MIO是否支持PvmiClockExtensionInterfaceUuid的扩展,而对于AndroidSurfaceOutput的查询来说:

PVMFCommandId AndroidSurfaceOutput::QueryInterface(const PVUuid& aUuid, PVInterface*& aInterfacePtr, const OsclAny* aContext)

{

    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "AndroidSurfaceOutput::QueryInterface() called"));

 

    PVMFCommandId cmdid = iCommandCounter++;

 

    PVMFStatus status = PVMFFailure;

    if (aUuid == PVMI_CAPABILITY_AND_CONFIG_PVUUID)

    {

        PvmiCapabilityAndConfig* myInterface = OSCL_STATIC_CAST(PvmiCapabilityAndConfig*, this);

        aInterfacePtr = OSCL_STATIC_CAST(PVInterface*, myInterface);

        status = PVMFSuccess;

    }

    else

    {

        status = PVMFFailure;

    }

 

    CommandResponse resp(status, cmdid, aContext);

    QueueCommandResponse(resp);

    return cmdid;

}

除了PVMI_CAPABILITY_AND_CONFIG_PVUUID都不支持

OK,到这里基本上Node层即MIO的处理就完毕了,现在要回到引擎

抱歉!评论已关闭.