2.2 从创建到setDataSource过程
本节分析的是从MediaPlayer创建到MediaPlayer调用setDataSource的过程。
在以前的相关图书中总是把时序图放在最后用于总结,但这样会让人觉得开始时没有一个概览,无从下手,所以本节先附上时序图,一步一步地对着时序图介绍每个阶段。
2.2.1 从创建到setDisplay过程
MediaPlayer时序图一(create到setDataSource过程,后面章节还有时序图,故这么命名),如图2-2所示。
图2-2 从create到setDisplay过程的时序图
从时序图可以看到,通过getService从ServiceManager获取对应的MediaPlayerService,然后调用native_setup函数创建播放器,接着调用setDataSource把URL地址传入底层。当准备好后,通过setDisplay传入SurfaceHolder,以便将解码出的数据放到SurfaceHolder中的Surface。最后显示在SurfaceView上。
2.2.2 创建过程
当外部调用MediaPlayer.create(this, "http://www.xxx.mp4")时,进入MediaPlayer的创建过程:
以上代码可以总结为,当MediaPlayer通过create的方式创建播放器时,内部new出MediaPlayer对象,并setDataSource,做好prepare的动作。这时外部只需调用start函数,就能播放音视频资源了。
实例化MediaPlayer有如下两种方式。
(1)可以使用直接new的方式:
(2)也可以使用create的方式,如:
上面两种实例化MediaPlayer的方式,都要经过new MediaPlayer,下面看看构造中做了什么操作。
播放页处理主页面:
接下来看Native层如何创建一个MediaPlayer。在介绍native_setup之前,请注意一般都是在静态代码块中加载.so文件的,在MediaPlayer中有一段静态代码块,用于加载和链接库文件media_jni.so,早于构造函数,在加载类时就执行。一般全局性的数据、变量都可以放在这里。下面是加载和链接media_jni.so文件的代码:
下面开始进入android_media_MediaPlayer.cpp分析,第一个函数android_media_Media-Player_native_init就是从Java静态代码块调过来的native_init:
上面这种方式是通过JNI调用Java层的MediaPlayer类,然后拿到mNativeContext的指针,接着调用了MediaPlayer.java中的静态方法postEventFromNative,把Native的事件回调到Java层,使用EventHandler post事件回到主线程中,用软引用指向原生的MediaPlayer,以保证Native代码是安全的。代码如下:
之前我们在Java层的MediaPlayer.java文件的构造函数中,分析到最后有一个native_setup,在android_media_MediaPlayer.cpp中找到对应的函数,代码如下:
可以看到会设置一些回调用的listener及创建C++中的MediaPlayer对象。
2.2.3 setDataSource过程
上面就是MediaPlayer的构造过程。构造后接下来要设置数据源,进而到了setDataSource过程,下面看看setDataSource做了什么操作:
先看看setDataSource中传入的参数是文件描述符的情况:
开始进入JNI层,发现找不到android_media_MediaPlayer_setDataSource函数,但发现有一个函数名映射函数声明,这是JNI中常用的动态注册方法,代码如下:
对以上这个函数名映射,如果读者看过JNIEnv * 源码的话,应该不会感到陌生,无非还是映射,不影响我们的分析。在这里接下来对android_media_MediaPlayer_setDataSourceFD函数进行分析:
接着分析process_media_player_call函数:
总结以上代码:当mp->setDataSource(fd, offset, length)函数得到状态后,对各种状态进行通知。有异常的直接抛出,这样也就不会影响MediaPlayer后面的执行过程了。
接下来看看以HTTP/RTSP传入JNI。在Java层中对应的nativeSetDataSource函数如下:
在JNI中通过映射表可对应到android_media_MediaPlayer_setDataSourceAndHeaders函数:
至此,setDataSource过程就完成了。这里需要注意两点,一点是从Java→JNI→C++的正向调用过程(前面从Java层到Native层都是正向过程),一点是C++→JNI→Java的过程(如mp->setDataSource( httpService, pathStr, headersVector.size() > 0? &headersVector : NULL),那有读者肯定会问,这样来回调的好处是什么?好处有如下这几点。
• 安全性,封装在Native层的代码是so形式的,破坏性风险小。
• 效率高,在运行速度上C++执行时间短,且底层也是用C++语言编写的。对于复杂的渲染及对时间要求高的渲染,放在Native层是最好不过的选择。
• 连通性,正向调用将值传入,反向调用把处理过的值通知回去。相当于一根管道。
2.2.4 setDisplay过程
接下来看看在setDataSource之后,开始进行的mp.setDisplay(holder):
对于上面代码中的第2点,同样在android_media_MediaPlayer.cpp中找到其对应的函数:
这里有如下几个概念需要理解。
• SurfaceTexture:SurfaceTexture是Android 3.0(API 11)加入的一个类。这个类跟SurfaceView很像,可以从视频解码里面获取图像流(image stream)。但是,和SurfaceView不同的是,SurfaceTexture在接收图像流之后,不需要显示出来。SurfaceTexture不需要显示到屏幕上,因此我们可以用SurfaceTexture接收解码出来的图像流,然后从SurfaceTexture中取得图像帧的副本进行处理,处理完毕后再送给另一个SurfaceView用于显示。
• Surface:处理被屏幕排序的原生的Buffer,Android中的Surface就是一个用来画图形(graphic)或图像(image)的地方。对于View及其子类,都是画在Surface上的,各Surface对象通过SurfaceFlinger合成到frameBuffer。每个Surface都是双缓冲的(实际上就是两个线程,一个渲染线程,一个UI更新线程),它有一个backBuffer和一个frontBuffer。在Surface中创建的Canvas对象,可用来管理Surface绘图操作,Canvas对应Bitmap,存储Surface中的内容。
• SurfaceView:在Camera、MediaRecorder、MediaPlayer中SurfaceView经常被用来显示图像。SurfaceView是View的子类,实现了Parcelable接口,其中内嵌了一个专门用于绘制的Surface,SurfaceView可以控制这个Surface的格式和尺寸,以及Surface的绘制位置。可以理解Surface就是管理数据的地方,SurfaceView就是展示数据的地方。
• SurfaceHolder:顾名思义,是一个管理SurfaceHolder的容器。SurfaceHolder是一个接口,其可被理解为一个Surface的监听器。通过回调函数addCallback(SurfaceHolder.Callback callback)监听Surface的创建,通过获取Surface中的Canvas对象,锁定之。所得到的Canvas对象在完成修改Surface中的数据后,释放同步锁,并提交改变Surface的状态及图像,展示新的图像数据。
最后总结一下,SurfaceView中调用getHolder函数,可以获得当前SurfaceView中的Surface对应的SurfaceHolder,SurfaceHolder开始对Surface进行管理操作。这里按MVC模式可以更好地理解M:Surface(图像数据)、V:SurfaceView(图像展示)、C:SurfaceHolder(图像数据管理)。MediaPlayer.java中的setDisplay操作就是对将要显示的视频进行预设置。
以上就是setDisplay的过程,Java层中setDisplay的最后一行,就是通过JNI返回的Surface,时时做好更新准备。