1. MediaPlayer介绍
MediaPlayer是Android api提供的用来播放音频和视频的类, VideoView也是对其简单的封装。 用MediaPlayer播放视频很简单, 需要4个步骤。
l 通过mediaPlayer.setDataSource设置要播放的uri。
l 通过mediaPlayer.setDisplay函数设置要显示到哪个surface。
l 通过mediaPlayer.prepare做播放前的准备。
l 通过mediaPlayer.play开始播放。
2. Media Framework整体架构
MediaPlayer位于应用程序的进程, 很可能会有多个位于不同进程的多个MediaPlayer实例存在。MediaPlayerService位于mediaserver进程,系统中只有单一的实例。mediaserver进程在系统启动时就开始运行media相关的服务,代码如下:
:
main_mediaserver.cpp: int main(int argc, char** argv) { sp<ProcessState> proc(ProcessState::self()); sp<IServiceManager> sm = defaultServiceManager(); LOGI("ServiceManager: %p", sm.get()); AudioFlinger::instantiate(); MediaPlayerService::instantiate(); CameraService::instantiate(); AudioPolicyService::instantiate(); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); } |
对于每一个MediaPlayer实例的请求, MediaPlayerService都会new一个StageFrightPlayer实例去接待。StageFrightPlayer仅充当adapter的角色,将工作代理给AwesomePlayer来做。
3. 深入分析AwesomePlayer
MediaPlayer的四部曲: setDataSource,setDisplay, prepare和play。
同样, AwesomePlayer中也有四部曲与之对应: setDataSource, setISurface,prepare, play。
AwesomePlayer将DataSource的缓冲单独放在一个进程中进行, 将音视频分离,视频解码, 视频渲染都放在一个线程中进行。音频解码和播放在其它线程中进行, 视频依据音频播放的时间戳来同步播放视频。
setDataSource过程:没做什么工作, 只是把参数uri存进mUri中。
setISurface过程:mISurface = isurface
图:AwesomePlayer prepare过程的数据流程图
prepare过程:如上图所示。
l 根据mUri来newDataSource的子类,当uri以http://开头时, 就会创建NuHttpDataSource, 就可以从mDataSource中读到媒体文件的原始数据。
l 调用工厂方法MediaExactor.create根据从mDataSource读到的数据判断文件格式来new MediaExactor的子类, MP4格式就对应MEPG4Exactor, 用它来分离mDataSource最终得到视频轨道mVideoTrack和音频轨道mAudioTrack, 就可以单独从mVideoTrack和mAudioTrack中读到未经解码的音频和视频数据。
l 从mVideoTrack和mAudioTrack可以获得编码格式, 通过OMXCodec::Create来生成mVideoSource和mAudioSource, 就能从mVideoSource和mAudioSource就可以读到解码后的音视频数据了,现在一切prepare好了。
Play过程: AwesomePlayer将mAudioSource交给AudioPlayer播放,而自己创建线程去主动与AudioPlayer取得同步, 通过mVideoSource读取解码后的数据, 然后交给mVideoRenderer(AwesomeRenderer)把数据render到据有的surface上。
图:AwesomePlayer的组成架构图,图片太大,如显示不全可右键下载下来看
上图充满了一种设计模式叫Decorator(装饰),NuCachedSource2装饰了DataSource, 给它添加了缓冲功能。 MP3Decoder和OMXCodec装饰了MediaSource, 给它添加了解码功能。Decorator模式一般都是通过继承+聚合(或组合)实现的, 能看出设计模式,才能对代码为什么张成这样有理解。
上图中红色框框中的部分是系统的变动点,可以把框框里的部分比作树叶, 框框外的部分比作树干,树叶每年更换, 树干主体相对不变, 而最接近树叶的连接处需要应树叶之变。
如果要增加新的协议,只需在协议引擎长出一只树叶,即写个DataSource子类, 实现抓取数据的接口, 并在树干上张出和树叶相连的节点, 即在AwesomePlayer.onPrepareAsyncEnent根据uri来创建datasource。
如果要新增对其它媒体格式的支持, 比如flv, 需要在音视频轨道分离引擎新增flv对应的MediaExactor子类,如果用软解码器, 需要在软解码引擎部分写个子类继承并聚合MediaSource来给它穿上解码的“Decorator”。如果用硬解码器,就有两种方法,符合现有框架的方法是在硬解码引擎部分添加OMXPlugin。还有一种方法, 那就是将OpenMax一不做二不休的踢出去, 将蓝色矩形框表示的硬解码框架整个替换。
如果要利用硬件加速模块加速视频渲染过程(利用Overlay),就需要在渲染引擎的libstagefrighthw.so中实现VideoRenderer接口, 实例可参考“mydroid”/device目录下三星或htc的libstagefrighthw实现。
4. NuHttpDataSource的实现
NuHttpDataSource实现了DataSource的接口, 并新增了一个public函数connect, connect函数在prepare过程中被调用。Connect过程中首先通过HttpSream.connect建立socket连接, 然后给uri发送http get请求, 等待服务器的回应, 分析回应中的http head, 处理redirect的情况, 还从http head中得到文件长度等信息。
最重要的就是实现的DataSource的readAt接口,NuHttpDataSource的readAt通过HttpStream接口读网络数据, HttpStream则通过读socket来实现。
5. NuCachedSource2的智慧
NuCachedSource2可以说是Decorator模式的经典范例, 继承了DataSource的接口, 同时又聚合了一个将被Cached的DataSource, 这个DataSource在构造函数中传入, 如下所示。
struct NuCachedSource2 : public DataSource {
NuCachedSource2(constsp<DataSource> &source);
……
}
在创建之初, NuCachedSource2就开始对DataSource进行缓冲, 它首先创建一个Alooper, Alooper内涵一个线程, 然后把一个AHandlerReflector挂在这个looper上跑, 最后发送kWhatFetchMore命令来启动缓冲。
NuCachedSource2::NuCachedSource2(constsp<DataSource> &source)
:mReflector(newAHandlerReflector<NuCachedSource2>(this))
: mSource(source),
mLooper(new ALooper),
……
mLooper->setName("NuCachedSource2");
mLooper->registerHandler(mReflector);
mLooper->start();
Mutex::AutolockautoLock(mLock);
(newAMessage(kWhatFetchMore, mReflector->id()))->post();
}
当mLooper的线程收到kWhatFetchMore命令时, 就会调用mSource.readAt函数读取1 Page大小的数据, 把读到存放到PageCache::Page结构里, 然后把Page添加进PageCache。
PageCache的大小和Page的大小在NuCachedSource2.h里定义:
enum {
kPageSize =65536,//Page size
kHighWaterThreshold = 5 * 1024 * 1024,// PageCache上限
kLowWaterThreshold = 512 * 1024,
};
当缓冲了1 Page数据后, 在mLooper的线程里会给自己发送一条kWhatFetchMore消息,之后继续上面的过程, 直到PageCache满或停止播放。
当调用NuCachedSource2的readAt函数读数据时, 就会从PageCache拿出Page里的数据给reader, 然后释放掉Page。
6. 支持html5网页视频的方法
首先, 浏览器访问视频网站时要上报ipad的useragent, 如果视频网站支持html5, 就会回传含video标签的html5网页。
处理含video标签的html5网页的流程如下:
含video标签的html5网页->本地webkit解析出url->Html5VideoProxy.java->WebView
->StageFright。
最终AweSomePlayer会得到一个m3u文件的url,何为m3u文件?请看百度百科。
下图为StageFright处理m3u列表的架构图。
AweSomePlayer收到url后调用LiveSource.isLiveSource判断是否是m3u文件,如果是就走LiveSource的流程。M3u列表中, 多个url可能对应同一视频的不同时间段,DISCONNECTIVITY标签意味着下个url是新视频文件的开始。 视频播放过程中, 当遇到DISCONNECTIVITY标签时,LiveSource会插入一段128(一个ts package大小)字节的空包, Mpeg2TsExtractor读到空包时,会通知Parser。
AwesomePlayer中,Decoder读Parser时如果Parser返回INFO_DISCONNECTIVITY, 就会重置解码器, 准备解码下一段新视频。
7. StageFright中rtsp协议的支持
打开StageFright中的rtsp支持, 需要设置media.stagefright.enable-rtsp=1(or true)属性。
8. 参考文献
http://iamkcspa.pixnet.net/blog。
TODO:
Rtsp支持分析。
如何实现OMXPlugin。
Audio播放过程。