https://github.com/zytc2009/BigTeam_learning
分类: C/C++
2010-12-01 12:49:06
opencore是android系统的多媒体实现的核心,是完成多媒体内容播放,录制,视频电话的基础。android上的主要服务由一 个叫mediaserver的进程来完成,这个进程通过openbinder的方式来完成其他进程(例如音乐播放器)的请求。拿播放器举例。 mediaserver的实现在文件夹\frameworks\base\media\mediaserver下,整个代码只有几行。
int main(int argc, char** argv)
{
sp
sp
LOGI("ServiceManager: %p", sm.get());
AudioFlinger::instantiate();
MediaPlayerService::instantiate();
CameraService::instantiate();
AudioPolicyService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
主要是初始化一些实例,其他进程的IPC请求。
关于opencore下多媒体播放,在mediaserver进程里面只有一行代码:
MediaPlayerService::instantiate();
这行代码的作用是初始化一个MediaPlayerService类的实例,并接把他加入到系统的serveceManager中。
MediaPlayerService的具体实现在文件夹frameworks\base\media\libmediaplayerservice中。
在涉及到要播放一个具体的媒体文件时,调用的函数是:
MediaPlayerService::create,这个new 了一个Client 并且函数将它返回为sp
Client对象什么在文件MediaPlayerService.h中,并且是private类,说明它只被MediaPlayerService对象使用。
Client对象继承自BnMediaPlayer,而BnMediaPlaye又继承自BnInterface
而IMediaPlayer又是何许东东。
IMediaPlayer.cpp在文件夹frameworks\base\media\中,
而IMediaPlayer.h 在文件夹frameworks\base\include\media中。
IMediaPlayer.h中声明了一个IMediaPlayer的类,而它的函数又都是virtual,一看就是用来申明接口的。
MediaPlayerService::create函数调用以后,马上调用Client:setDataSource,其实现在MediaPlayerService.cpp中,
代码如下:
注意这行代码:sp
他的作用是根据不同的playerType来创建player实例,我们这里主要关注PVPlayer。
说了这么多,终于到达opencore那一层了。
有时候会觉得android这样的设计实在太复杂了,调用起来太麻烦,直接实现一个IMediaPlayer的类不就完了吗。
但是仔细一样,androide的这种设计方式其实有它在扩展性和可维护性上才这样做的。
说到底就是一句话,降低模块间的耦合性。
涉及到具体的操作,都是通过实现一个接口类来实现,这样具体的实例在创建的时候就可以通过工厂模式来简单的进行扩展。
例如上面所提到的createPlayer(playerType)这个函数,当你需要添加自己的特殊格式的播放器的时候,就不用来改它本来的代码,而只用在createPlayer(playerType)的实现下面几行代码:
非常方便,而这种扩展性和是由接口和实现的分离带来的,createPlayer
返回的是sp
扯远了。设计模式的厉害,可能需要我花整个的职业生涯来体会。
废话不多说,让我们来看PVPlayer的实现,我刚才说过,PVPlayer才是opencore真正的内容。
PVPlayer的申明在frameworks\base\include\media\PVPlayer.h中,而实现在external\opencore\android\playerdriver.cpp。
为什么要这样做?我不懂,我猜测还是为了实现和接口的分离,只不过这次的分离就只能简单的通过把头文件和实现文件放到不同文件夹下来实现。
OK,那么让我们来看看PVPlayer是干嘛的。
PVPlayer继承自MediaPlayerInterface,而MediaPlayerInterface是Opencore对媒体播放的抽象接口的声明类。
那么我们去看看PVPlayer的各个接口的实现吧。
PVPlayer的很多借口都是通过向它的成员mPlayerDriver发送命令来实现,而mPlayerDriver通过调用 mPVPlayer的sendEvent函数来告诉PVPlayer,命令是否执行成功了。PlayerDriver是PVPlayer的一个内部成员, 它是PVPlayer的命令执行者。
sendEvent的实现如下:
MediaPlayerBase::sendEvent的实现如下:
mNotify是MediaPlayerBase的一个成员变量,它是一个函数指针,原型如下:
这个成员变量在调用createPlayer的时候就调用setNotifyCallback来赋值的。
所以,可以看到,底层的事件会一层一层的往上调用,直至返回给用户层。
这里涉及到2个设计模式:
命令模式和观察者模式。
这两个模式都是设计模式中的基本模式之一,功能强大,逻辑清晰。具体的内容不是本文的重点,在此略过。
下一节介绍PVPlayer是怎样通过PVPlayerDriver来完成那些接口的
PlayerDriver类就是PVPlaer类的员工,PVPlayer就是PlayerDriver的老板,PVPlayer要干什么 事情根本不用自己操心,它就向PlayerDriver的工作表上发送一个一个的条目就行了,然后它就打球唱歌逍遥去了。然后听取一下 PlayerDriver的工作成果就ok了。
哎,我啥时候才能做到PVPlayer的那种程度啊...
废话不多说了,该干的还是得干,谁叫咱只是个PlayerDriver呢。
首先看看PlayerDriver类的声明:
这说明了什么?答案是:这说明了PlayerDriver要干的事情还真不少(这不是废话么)。
让我们逐个说明PlayerDriver的基类是干嘛的:
public OsclActiveObject:这个类的声明在文件external\opencore\oscl\oscl\osclproc\src \oscl_scheduler_ao.h,它说明PlayerDriver类的可以被加入到scheduler中执行...(这又是什么意思?)解释起 来太费事,容我稍后单独在一个章节中说明(先占位,以后给出链接)。
public PVCommandStatusObserver:
public PVInformationalEventObserver:
public PVErrorEventObserver:这三个类的声明在文件external\opencore\engines\common \include|pv_engine_observer.h中,它说明原来PlayerDriver是某个类的的观察者(原来PlayerDriver 也是个小boss,可以像手下发布命令)。
PlayerDriver的重要的成员变量如下:
PVPlayer *mPvPlayer; \\这是PlayerDriver的老板,给他发命令的家伙
PVPlayerInterface *mPlayer; \\这是PlayerDriver的手下,完成大部分的工作
PVPlayerDataSourceURL *mDataSource; \\如变量名所言:datasource
PVPlayerDataSink *mAudioSink; \\如变量名所言:datasink
PVMFNodeInterface *mAudioNode;
AndroidAudioMIO *mAudioOutputMIO;
PVPlayerDataSink *mVideoSink;
PVMFNodeInterface *mVideoNode;
AndroidSurfaceOutput *mVideoOutputMIO;
PvmiCapabilityAndConfig *mPlayerCapConfig;
PlayerDriver的构造函数和大多数类的构造函数一样,都是吧类的内存分配时往变量里分配的随机值改变成默认的0,以及创建一些公用的锁之类,但是其中要提到的是这一行:
createThreadEtc(PlayerDriver::startPlayerThread, this, "PV player");
createThreadEtc的声明在文件frameworks\base\include\utils\threads.h中,而它的实现在文件frameworks\base\libs\utils\Threads.cpp,它干的事情很简单,就是创建一个线程。
顺便说几句,opencore内部定义的oscl层就是用来进行threads,IO,memory等操作系统相关的操作的定义,它为什么要引用 android的内容呢?我的理解是:opencore也有它不完善的地方,或者说我所工作的这个版本的opencore是在android分支下,它本 来就和android绑定的比较紧。
这个线程最终调用到int PlayerDriver::playerThread()。
这个函数得仔细解释了,因为它的是整个PlayerDriver的工作线程,也就是执行命令,干活的那个线程。
OMX_MasterInit(); 初始化OpenMax 的主核心(Master Core),而这个主核心管理着若干从核心(Slave Core),每个从核心都可以是一个单独的OpenMax IL的实现,他们通过动态加载的方式被主核心加载进来,然后若干个实现并存于Opencore中,向上层提供codec层的接口。
OsclScheduler::Init("AndroidPVWrapper"); 初始化一个调度器,用来提供给PlayerDriver使用。这个调度器的工作线程就是当前线程,也即所有调度器中运行的内容都要通过这个调度器在这个线 程内完成。
OSCL_TRY(error, mPlayer = PVPlayerFactory::CreatePlayer(this, this, this)); 创建一个PVPlayerInterface类,在这里,它创建一个PVPlayerEngine的实例。这个类才是实际干活的,PlayerDriver只是一个传话的,最后的工作教导PVPlayerEngine中。
AddToScheduler(); 将自身(this)添加到调度器。
PendForExec(); 阻塞调度器。
OSCL_TRY(error, sched->StartScheduler(mSyncSem)); 开始干活喽。
剩下这些代码,不用解释就知道干嘛的了:
删除那些通过命令的中间过程而申请的资源,或者说创建的对象。
PVPlayer通过调用PlayerDriver::enqueueCommand来传递命令给PlayerDriver。
PlayerDriver则通过PlayerDriver::handleXXXXXXXXX来处理各个命令。
PlayerDriver能通过吧PVPlayer调用传递过来的参数转换成PVPlayerInterface能识别的参数,再传递给PlayerDriver:mPlayer来完成各个命令。
PlayerDriver类里有如下成员变量的声明:PVPlayerInterface *mPlayer;它的创建是在调用函数int PlayerDriver::playerThread();的时候。由如下一行代码来完成的:OSCL_TRY(error, mPlayer = PVPlayerFactory::CreatePlayer(this, this, this));
PVPlayerFactory是一个工厂类(不清楚工厂类的朋友可以去看设计模式里面的工厂模式)。最后它调用 PVPlayerEngine::New(aCmdStatusObserver, aErrorEventObserver, aInfoEventObserver, aHwAccelerated);来得到PVPlayerEngine的一个实例。它在创建的时候同时PlayerDriver的this指针传给 PVPlayerEngine作为参数,所以它同时也是PVPlayerEngine的观察者,这样PlayerEngine里发生了什么事情,可以通过 观察者的接口传递上去。
这里有个疑问,为什么不用PVPlayerEngine的构造函数来产生新的实例,而要通过PVPlayerEngine的一个static函数来完成呢?我的理解是,为了保证PVPlayerEngine是封闭的,不可继承的。
在创建了PVPlayerInterface的实例以后,把它缴入到Scheduler中,然后一句sched->StartScheduler(mSyncSem),就完成了它在整个生命周期中应该干的事情(又见scheduler)。
PVPlayerEngine,是由若干PVMF nodes和对nodes进行管理的功能模块组成,每个node完成独特的工作,它是一个独立的功能块,若干个node一起组成了一个node graph,这个graph是进行媒体播放的核心部分。同事PVPlayerEngine还完成一些额外的工作,例如node的管理(包括搜索,注册,创 建等),graph的创建。
在Opencore官方文档《pvplayer_developers_guide.pdf》中,有如下的状态转换图:
为了理解这个状态图,我们可以从一个文件播放的流程来看它每个步骤干了些什么事情:
按上图的状态转换方式,一次媒体播放过程有如下步骤组成:
AddDataSoure->Init->AddDataSink->Prepare->Start ->Pause->Resume ....... ->Stop->RemoveDataSink->RemoveDataSource
1,PVPlayerEngine::AddDataSource
从名字上就能理解,它是在一开始的时候给他提供要播放的数据来源,这里的数据来源可以是本地文件,也可以是流媒体文 件。PVPlayerEngine的客户端是通过给他发送命令的方式来执行的,内部执行命令的函数是:PVMFStatus PVPlayerEngine::DoAddDataSource(PVPlayerEngineCommand& aCmd)。
在这个函数里面,首先进行差错检测,然后看Source的PVMFFormatType,在PlayerDriver接收到播放请求的URL以后它 会判断URL是什么类型的,如果是"rtsp://"开头的,则设置类型为:PVMF_MIME_DATA_SOURCE_RTSP_URL,如果URL 是以".sdp"结尾的,那么类型则被设置为:PVMF_MIME_DATA_SOURCE_SDP_FILE,如果是"http:"则这是一个无效的, 其他的统一设置为:PVMF_MIME_FORMAT_UNKNOWN。
那么对于PVMF_MIME_FORMAT_UNKNOWN类型,PVPlayerEngine则要自己去判断是这个媒体文件时什么类型的,例如它 是mp4文件,还是avi文件等。这些操作室通过PVPlayerEngine的子函数DoQuerySourceFormatType来执行的。这个函 数很复杂,涉及到整个框架的内部子框架:FormatRecognizer。这个子模块又用到了那些耳熟能详的设计模式:工厂模式,命令模式,观察者模 式,单件模式,等等等。具体怎么做我们不去暂时不是深究,因为这个内容比较庞杂。我们要记住的是,这个函数调用到了PVMFStatus PVPlayerRecognizerRegistry::QueryFormatType接口,并且把this指针传递下去作为 FormatRecognizer执行文件格式检测以后的报告对象。总之,经过各种繁琐的类的构造和释放,命令的转换...最后终于识别成功了,于 是,Recoginzer通知PVPlayerRecognizerRegistryObserver,格式识别出来了,由于 PVPlayerRecognizerRegistryObserver是一个
接口类,PVPlayerEngine类从这个类中继承而来,所以,我们看PVPlayerEngine::RecognizeCompleted这个函数就行了。
关注这一行:PVMFStatus retval = DoSetupSourceNode(cmdid, cmdcontext);在文件格式被正确识别出来以后,就需要来创建SourceNode了。
在node被创建以后,并且它的接口也被正常配置了以后,SourceNode会报告 PVMFInfoDataReady事件给PVPlayerEngine。(这中间经历了若干过程,一系列的Command 和Observer,过程类似,具体的可以看代码)。一直到最后调用到函数:DoSourceNodeInit(),sourcenode会受到Init 的命令,然后告诉Observer即engine,这个过程通过函数PVPlayerEngine::HandleSourceNodeInit来实现。 SourceNodeInit成功后,PVPlayerEngine的状态转变成PVP_ENGINE_STATE_INITIALIZED。
这里插一句,PVPlayerEngine的很多调用都是通过发送一个命令给他的一个成员类实例,然后这个成员类实例 再调用PVPlayerEngine对应的Observer接口来完成操作,因为这些所有的操作都在一个线程里面实现,所有的调度都通过 Scheduler实现,而它本身的所有的成员,也都由这个Schedule来进行调度,整个运行只有一个线程,所以不会出现同步的问题,因此我们也见 到,在PVPlayerEngine实现里,没见到任何的mutex锁,佩服packervideo 公司的的程序设计设计人员的精巧框架。在PVPlayerEngine里面,很多形如:Doxxxxxx, Handlexxxxxx的函数,一般可以配对来看。这样就不至于被各种类,接口给绕进去。
2,PVPlayerEngine::Init
这个函数干的事情很简单,就是去调用SourceNode的Init函数。然后这个函数成功了以后调用 HandleSourceNodeInit,然后调用DoSourceNodeGetDurationValue,然后就是 HandleSourceNodeGetDurationValue。如果这一切没问题,OK,Engine已经是 PVP_ENGINE_STATE_INITIALIZED了。
3,PVPlayerEngine::AddDataSink
这也是个相对简单的函数,没啥好说的。
4,PVPlayerEngine::DoPrepare
这个函数比较复杂,它要完成的事情很多,这个函数的注释指明:它有4个小步骤分别完成以下事情(这是原函数中的注释的内容,具体的实现得仔细去看):
a, When Engine is in PVP_ENGINE_STATE_INITIALIZED state, here engine will start the Track Selection, which will start with Sink Nodes。
b, After Init completes on Sink nodes Engine will be in PVP_ENGINE_STATE_TRACK_SELECTION_1_DONE and Engine will start creating Dec nodes and call Init on dec nodes, if needed。
这个步骤有必要说明一下,它会首先尝试看sink node能不能支持source node的多媒体流,如果能播放,则不必要进行到dec node的选择,如果不能播放,则必须要查询,看有没有合适的dec node,它的输入和source node匹配,输出和sink node匹配。
c, Init completion on Dec nodes will take Engine to PVP_ENGINE_STATE_TRACK_SELECTION_2_DONE and Engine will start populating the Playable List after verifying parameters of different tracks. Engine after selecting tracks will call Reset on Sink and Dec nodes。
d, Once Reset completes on Sink and Dec nodes, Engine will be in PVP_ENGINE_STATE_TRACK_SELECTION_3_DONE and then Engine will delete all the unused dec nodes.
上述的这些操作,很多都是异步方式执行的,也就是,等上述步骤执行完成以后,相应的命令才能执行完成。
5,PVPlayerEngine::DoStart
启动iPlaybackClock,开始播放。
6,PVPlayerEngine::DoPause
7,PVPlayerEngine::DoResume
暂停和继续。
至于剩下的操作,就是按照相反的操作进行,要相对简单的多,不用去完成如此多的检测和状态的转换。
关于这个,opencore的目录下有文档quick_start.txt有简要的说明,先把那段话引用下来:
As part of the OpenCORE release, PacketVideo also provides it's internal
build system that can be used to build OpenCORE outside of Android with the
native linux toolchain.
The following are the steps involved:
NOTE 1: Assume
OpenCORE codebase
NOTE 2: The setup scripts are meant to be used in with the bash shell.
cd
source setup.sh
make -j
This will build all the required libraries in
and the executables in
意思很简单,就是到build_config/opencore_dynamic目录下运行setup.sh 然后运行make -j。
这里有个问题,如果在32位的操作系统上,这样做是能运行成功的,如果在64位系统上,会出现以下的错误:
“error: cast from 'OsclAny*' to int32 loses precision” 这是因为编译器是编译成了64位的,解决的办法是采用32位的编译器。
如果你是要移植到你自己的linux嵌入式设备上,那么就需要改一些内容了。
1,export ARCHITECTURE=XXXX (例如 linux_arm)。
2,在tools_v2/build/make/platforms目录下,加上XXXX.mk这里的XXXX必须和上一条的一致。然后在 XXXX.mk里面加上对应的内容,主要就是交叉编译器的命令,具体的可以参考tools_v2/build/make/platforms /linux_arm.mk的内容。
3,在oscl/oscl/oscl/config/目录下,添加XXXX文件夹,然后把oscl/oscl/oscl/config/linux下的内容copy进去,然后做相应的更改。
4,然后make -j就行。