android 多媒体框架服务之StagefrightPlayer和OMXCodec实现原理学习

android 多媒体框架服务之StagefrightPlayer和OMXCodec实现原理学习

2016-08-30 08:01 来源:互联网

1.1StageFright和openCore和NuPlayer的关系


上图可知,stagefright是在MediaPlayerService这一层加入的,和opencore是并列的,在选用opencore还是stagefright的代码切换上也非常容易。

Android上的MediaPlayer播放底层框架已经经历了多次变动,从最早先的OpenCore到后来的StageFright再到现在的NuPlayerDriver,在工作开始接触Android的时候已经移除了OpenCore所以对OpenCore的了解仅仅停留在听说过,这些框架在演进过程中一般都是先两种框架并存,然后再在某个版本中将其移除,早先Android中使用的是Stagefright + NuPlayer并存的方式,其中前者负责播放本地的媒体文件,后者用于播放网络流媒体文件,但是在后来的Android L开始NuPlayeri渐渐开始替代了Stagefright,目前本地播放已经切换到NuPlayer上了,在Android N AOPS 源代码中甚至移除了Stagefright。

1.2 OpenMAX


OpenMAX简介
android系统中的编解码器部分用的是openmax,以后会深入了解。openmax是一套标准接口,各家硬件厂商都可以遵循这个标准来做自己的实现,发挥自己芯片特性。然后提供给android系统来用。因为大部分的机顶盒芯片产品硬件的编解码是它的优势,可以把这种优势完全融入到android平台中。以后手机高清视频硬解码也会是个趋势。
第一层:OpenMaxDL(DevelopmentLayer,开发层)
第二层:OpenMaxIL(IntegrationLayer,集成层)
第三层:OpenMaxAL(ApplictionLayer,应用层)
OpenMaxIL 处在中间层的位置,OpenMAX IL作为音频,视频和图像编解码器能与多媒体编解码器交互,并以统一的行为支持组件(例如资源和皮肤)。这些编解码器或许是软硬件的混合体,对用户是的底层接口应用于嵌入式或/和移动设备。它提供了应用程序和媒体框架,透明的。本质上不存在这种标准化的接口,编解码器供应商必须写私有的或者封闭的接口,集成进移动设备。IL的主要目的是使用特征集合为编解码器提供一个系统抽象,为解决多个不同媒体系统之间轻便性的问题。
refenence2 .

1.3 StageFright

基于Stagefight的MediaPLayer框架的结构:

stageFright is a player .

上图可以看出播放过程主要涉及3个进程: app端进程,媒体框架服务(stageFright),OMX服务.实际使用还经常用到一个专门做跨进程内存共享管理的进程(MemoryDeal).注意后面会经常讲到”客户端”,有时并不是指app端,要区分开来.
- 应用层和framework层?使用到MediaPlayer的应用很多,最常见的就是Music和Video,如果要了解这些应用的实现可以看下AOSP代码中的packages/apps,这些代码中用到了frameworks/base/media/所提供的MediaPlayer接口,这些接口都十分简单,我们只需要知道这些接口的具体功能就可以开发出一款功能较为齐全的Music Player.
- Native Media Player 层:
应用层的native实现.通过binder机制和Service交互.

Media Player Service 部分:
从Native层发出的IPC请求将会由Media Player Service 部分进行处理.MediaPlayerService是在frameworks/av/media/mediaserver/main_mediaserver.cpp的main方法中初始化的,在main方法中还启动了多个Android系统服务比如AudioFlinger, CameraService等,实例化Media Player Service 子系统的工作包括MediaPlayerService对象的创建,以及内置底层Media PLayer播放框架工厂的注册,一旦 MediaPlayerService 服务启动,MediaPlayerService将会接受Native MediaPlayer 层的IPC请求,并且为每个操作media内容的请求实例化一个MediaPlayerService::Client对象, Client有一个createPlayer 的方法可以使用特定的工厂类为某个特定的类型创建一个本地media player,后续的发向native层的请求都会交给刚刚提到的native 层的 media palyer来处理,这里的media player指的是StagefrightPlayer或者Nuplayerdriver.但是我们这里先不讨论Nuplayerdriver。 分析文件:

StageFright主要是对AwesomePlayer的封装.AwesomePlayer是事件驱动的播放器.本文主要分析它对视频流的处理.重点在AwesomePlayer::onVideoEvent函数.其中从流中read packet,parse,decode .都在mVideoSource->read(&mVideoBuffer, &options);函数完成.该函数在OMXCodec.cpp实现.其中read(extract)和parse 在AwesomePlyaer调用其它组件(如MPEG4Extractor)完成,参数mVideoBuffer即为解码后的帧图像,decode则是调用OMXCodec的服务接口.也就是解码时又通过Binder做了一次跨进程通信.关于OMXCodec Service的一些文件:
- 接口定义:
IOMX.h
- 客户端类:
OMXCodec.cpp
OMXClient.cpp
IOMX.cpp (BpOMX类/BnOMX类)
- 服务端类:
OMX.cpp
OMXNodeInstance.cpp

示例函数
fillOutpuBuffer. 见源码书签和注释.

以上类是通过Binder机制实现的.因为这我第一个学习的android Framework.下面先简单介绍Binder机制:

2. Binder机制简单介绍

binder是android 系统下的一种IPC机制。是进程间交互的一种方式。在开发android应用时,脑袋一定要一直保持C/S结构的思想。
android应用的开发说白了就是通过android提供的一系列的服务来完成自己的目的,咱们刚才也的那个播放器的apk也是需要android提供的播放器的服务来完成的。
apk是一个独立的进程,android的系统服务也是很多个独立的进程。binder的功能就是把client 和 service 连接起来。

2.1 入门实例

Binder简单实例 - lansehai的专栏 - 博客频道 - CSDN.NET

2.2 总结

a. IInterface 的继承类里声明将要跨进程调用的函数.声明为纯虚函数. (如IOMX.h中IOMX类).
a. 如果服务端和客户端的创建都在同一个进程中,interface_cast会直接获得xx的Bn实例,就是相当于直接声明了一个xx类型。OMXClient#getOMX()
c. BnBinder是Service端接口.binder是代理(BpBinder).Service实现业务功能.客户端需要实现发送功能.

2.3 Imemory

调用OMXCodec Component需要进程间共享内存.android在实现进程共享内存时使用Binder和匿名共享内存实现了一套共享内存机制.

MemoryHeapBase和MemoryBase.前者是匿名共享内存类,以页为单位.后者是基于MemoryHeapBase的匿名共享内存,使用偏移值表示.ta解决多个clinets一个Service 内存共享问题.

重要函数总结:

getMemory
成员函数getMemory用来获取内部的MemoryHeapBase对象的IMemoryHeap接口.如果成员变量mHeap的值为NULL,就表示这个BpMemory对象尚未建立好匿名共享内存,于是,就会通过一个Binder进程间调用去Server端请求匿名共享内存信息,在这些信息中,最重要的就是这个Server端的MemoryHeapBase对象的引用heap了,通过这个引用可以在Client端进程中创建一个BpMemoryHeap远程接口,最后将这个BpMemoryHeap远程接口保存在成员变量mHeap中,同时,从Server端获得的信息还包括这块匿名共享内存在整个匿名共享内存中的偏移位置以及大小。这样,这个BpMemory对象中的匿名共享内存就准备就绪了。

pointer()
成员函数pointer()用来获取内部所维护的匿名共享内存的基地址;

size()
成员函数size()用来获取内部所维护的匿名共享内存的大小; offset()
用来获取内部所维护的这部分匿名共享内存在整个匿名共享内存中的偏移量。 使用实例:

在BpSharedBuffer类的成员函数transact中,向Server端发出了一个请求代码为GET_BUFFER的Binder进程间调用请求,请求Server端返回一个匿名共享内存对象的远程接口IMemory,它实际指向的是一个BpMemory对象,获得了这个对象之后,就将它返回给调用者;
在BnSharedBuffer类的成员函数onTransact中,当它接收到从Client端发送过来的代码为GET_BUFFER的Binder进程间调用请求后,便调用其子类的getBuffer成员函数来获一个匿名共享内存对象接口IMemory,它实际指向的是一个MemoryBase对象,获得了这个对象之后,就把它返回给Client端。
(详细示例见链接最后一节)

2.4 IOMX

IOMX定义了OMXCodec的接口.如fillBuffer,emptyBuffer.

3. StageFright底层具体实现


说白了既然StageFright是个播放器,那么它至少有4大部分:source、demux、decoder、output。
1.source:数据源,数据的来源不一定都是本地file,也有可能是网路上的各种协议例如:http、rtsp、HLS等。
2.demux解复用:视频文件一般情况下都是把音视频的ES流交织的通过某种规则放在一起。这种规则就是容器规则。现在有很多不同的容器格式。如ts、mp4、flv、mkv、avi、rmvb等等。demux的功能就是把音视频的ES流从容器中剥离出来,然后分别送到不同的解码器中。
3.decoder解码:解码器–播放器的核心模块。分为音频和视频解码器。
4.output输出:输出部分分为音频和视频输出。解码后的音频(pcm)和视频(yuv)的原始数据需要得到音视频的output模块的支持才能真正的让人的感官系统(眼和耳)辨识到。
所以,播放器大致分成上述4部分。怎么抽象的实现这4大部分、以及找到一种合理的方式将这几部分组织并运动起来。是每个播放器不同的实现方式而已。

AwesomePlayer是实现播放的底层操作者,它在StagefrightPlayer初始化的时候被创建,它负责将对应的音频视频和对应的解码器对应起来。这里涉及到了MediaExtractor,它会从媒体文件中抽取到有效的头信息。并返回对应的引用。在准备播放的时候AwesomePlayer通过OMXCodec来根据媒体文件类型创建解码器,解码器是驻留在OMX子系统上(OMX是OpenMAX在Android上面的实现),这些解码器主要用于处理内存缓冲,转化成原始数据格式,这部分的实现代码主要在frameworks/av/media/libstagefright/omx 以及frameworks/av/media/libstagefright/codecs 目录下, Stagefright Media Player和 OMX部件(Component)是通过IPC方式交互的.
AwesomePlayer最终会处理应用层发出的播放,暂停,停止等请求,这些请求往往和媒体类型有关联对于音频文件.AwesomePlayer 将会创建一个AudioPlayer来对文件进行处理,比如当前文件只有音频部分需要播放,这时候AwesomePlayer将会调用AudioPlayer::start()进行播放,一旦用户提交了其他新的请求AudioPlayer会使用MediaSource对象来和底层的OMX子系统进行交互。

3.1 StageFright和OMXCodec通信StageFright数据处理流程


重点在read函数.从流中read packet,parse,decode .都在mVideoSource->read(&mVideoBuffer, &options);函数完成.

3.2底层进程通信对缓存的管理:

read函数将数据读到缓存并处理后,如何传到OMXCodec Servcie?
read循环最终调用了:OMXCodec::drainInputBuffer(BufferInfo *info),最终通过emptyBuffer传递缓存.

status_t emptyBuffer(node_id node,buffer_id buffer,OMX_U32 range_offset, OMX_U32 range_length,OMX_U32 flags, OMX_TICKS timestamp)

通过分别分析客户端和服务端的emptyBuffer函数得到以下信息:
服务端:
a. 约定buffer_id 为OMX_BUFFERHEADERTYPE *指针.
客户端
如何对应客户端读出的缓存给服务端?

err = mOMX->emptyBuffer(mNode, info->mBuffer, 0, offset,flags, timestampUs);

mBuffer即为BufferId.实质为指针.服务端转为OMX_BUFFERHEADERTYPE *类型后,调用CopyToOMX(header),调用OpenMAX接口,将输入buffer拷贝到Codec硬件内存.

解码后调用fillOutputBuffer将缓存从Codec拷贝回来,同样是跨进程调用,实现函数是fillBuffer.服务端fillBuffer实现把缓存拷贝到对应的buffer_id.

如何初始化缓存?

![(“>==/6599299576238819530.png)~~
客户端allocateBuffersOnPort函数初始化缓存池.这个函数比较重要,直接画图来看他的调用和IPC通信:

Created with Rapha?l 2.1.0AwesomePlayerAwesomePlayerOMXCodecOMXCodecMemoryDealerMemoryDealerOMXClient.cppOMXClient.cppIOMX.cppIOMX.cppOMX.cppOMX.cppOMXNodeInstance.cppOMXNodeInstance.cpp媒体框架进程allocateBuffersOnPortnew MemoryDealer();然后调用MemoryDealer::allocate函数分配得到一个Imemory指针spmOMX->allocateBuffer[backup]这里还封装了一次,只有使用remote Codec时才跨进程调用上一步中的判断也是使用远程Codec时调用allocateBufferBackupgetOMX(node)->allocateBufferBackupallocateBufferbackup 这里就是跨进程调用了.IOMX.cpp里BpIOMX::allocateBufferbackup 使用writeInt32等完成发送,Service端BnBinder::OnTransact也在这里实现接收.其中最特别的又是writeStrongBinder,直接把Imemory对象发送出去,服务端得到一个Imemory的客户端BpBinder,以便可以访问共享内存.allocateBufferbackup继续封装

note right of OMXNodeInstance.cpp:调用Codec接口的地方,也就是调用芯片厂商提供的接口.
例如OMX_AllocateBuffer分配得到.OMX_BUFFERHEADERTYPE 指针.再通过reply写回客户端,也就是客户端可以得到一个OMX_BUFFERHEADERTYPE 的指针.OMX_BUFFERHEADERTYPE的一个重要成员BufferMeta将Imemory的共享内存封装进来.最后函数返回客户端一个buffer_id,实际就是一个在服务端保存的OMX_BUFFERHEADERTYPE指针.然后把这个指针绑定到对应的portIndex, MMediaPlayer使用时再绑定到对应的MediaInfo上.
这样下次OMXCodec调用drainInputBuffer这样的函数时,就能通过MediaInfo在OMXCodec Component 找到对应bufferId ,进而通过emptyBuffer这样的跨进程调用通知服务端(Component),服务端有bufferId后,转为BufferHeader指针后遍知道该使用那块共享内存了.

再补充几个其中的几个信息:

通过MemoryDealer分配匿名共享内存. 通过mOMX->allocateBufferWithBackup(mNode, portIndex, mem, &buffer);分配指定大小共享缓存.并绑定到buffer_id.(以下带mOMX 字样的都是IPC通信 ,对应的服务端函数在OMXNodeInstance.cpp).也就是客户端的每个bufferInfo保存了一个Service对应的OMX_BUFFERHEADERTYPE对象指针. 将客户端的共享缓存地址通过mem->pointer();赋值给info::mData. 注意MediaBuffer的初始化:info.mMediaBuffer = new MediaBuffer(info.mData, info.mSize);MediaBuffer::mData 即为info.mData.也就是MediaSource,例如MPEG4Extractor 的read /parse 操作也是直接对共享缓存操作. OMXCodec::allocateBuffersOnPort为每个buffer分配缓存和index.同时保存缓存信息到服务端:mOMX->storeMetaDataInBuffers. 以及把缓存队列传到MediaSource(此例是MPEG4Exctractor):mSource->setBuffers(buffers) 服务端分配缓存见OMXNodeInstance::allocateBufferbackup. 服务端的共享内存指针保存在BufferMeta buffer_meta.例如emptyBuffer调用CopyTOOMX可以把芯片缓存拷贝到共享内存缓存.

顺便学习以下如何写Binder机制中客户端的发送函数,一个BpBinder函数是长这样的:

virtual status_t allocateBufferWithBackup() {Parcel data, reply;data.writeInterfaceToken(IOMX::getInterfaceDescriptor());data.writeIntPtr((intptr_t)node);data.writeInt32(port_index);data.writeStrongBinder(params->asBinder());remote()->transact(ALLOC_BUFFER_WITH_BACKUP, data, &reply);status_t err = reply.readInt32();//...*buffer = (void*)reply.readIntPtr();return err; }

通过IMemory的学习可知,IOMX客户端通过MemoryDealer构建MemoryBase对象是Imemory的远程接口实现.而OMX Compoent的接收onTransact中,读取的是一个匿名共享内存对象的远程接口Imemory,它实质指向一个BpMemory对象.接收完通过interface_cast(data.readStrongBinder())转为Imemory调用.

//todo
- node_id 是啥? 怎么区分remoteOMX & localOMX?
OMXCodec.cpp不是直接调用OMXCodec的客户端接口.中间还封装了一个类OMXClient.cpp.当判断使用remoteOMX时才调用remote接口.即BpOMX.而BpOMX在IOMX.cpp实现.node_id是区分local/remote的关键.猜测noteId是标记媒体框架服务(MediaPlayer)的客户端程序.

Given a node_id and the calling process’ pid, returns true iff the implementation of the OMX interface lives in the same process.
猜测是如果同进程调用的话不再重新映射共享内存?

OMXObserver是干么用的?
如何读取packet,Source Input?
err = mSource->read(&srcBuffer, &options); 保存在 MediaBuffer::mData. MediaCodec和OMXCodec的关系.

其实在openmax接口设计中,他不光能用来当编解码。通过他的组件可以组成一个完整的播放器,包括sourc、demux、decode、output。
1. android系统中只用openmax来做code,所以android向上抽象了一层OMXCodec,提供给上层播放器用。
播放器中音视频解码器mVideosource、mAudiosource都是OMXCodec的实例。

2. OMXCodec通过IOMX 依赖binder机制 获得 OMX服务,OMX服务 才是openmax 在android中 实现。

3. OMX把软编解码和硬件编解码统一看作插件的形式管理起来。

由文中可知,MediaPlayer(stageFright),MediaCodec 都是调用OMXCodec,stageFright和MediaCodec是平行关系,无相互调用. OMXCodec和ACodec都是更底层的东西.

OMXCodec和ACocdec

NuPlayer调用ACodec(支持网络流).

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值