Android平台StagefrightPlayer的具体实现细节
1. 框架结构

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平台中。以后手机高清视频硬解码也会是个趋势。
第一层:OpenMax DL(Development Layer,开发层)
第二层:OpenMax IL(Integration Layer,集成层)
第三层:OpenMax AL(Appliction Layer,应用层)
OpenMax IL 处在中间层的位置,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和匿名共享内存实现了一套共享内存机制.
相关介绍:
Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析
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.
如何初始化缓存?

本文深入解析Android平台StagefrightPlayer的实现细节,包括其与OpenCore、NuPlayer的关系变迁、OpenMAX集成层的作用以及Stagefright底层的具体实现原理。特别关注了Stagefright与OMXCodec之间的通信机制和缓存管理。

8274

被折叠的 条评论
为什么被折叠?



