![Android音视频开发](https://wfqqreader-1252317822.image.myqcloud.com/cover/334/31186334/b_31186334.jpg)
2.2 从创建到setDataSource过程
本节分析的是从MediaPlayer创建到MediaPlayer调用setDataSource的过程。
在以前的相关图书中总是把时序图放在最后用于总结,但这样会让人觉得开始时没有一个概览,无从下手,所以本节先附上时序图,一步一步地对着时序图介绍每个阶段。
2.2.1 从创建到setDisplay过程
MediaPlayer时序图一(create到setDataSource过程,后面章节还有时序图,故这么命名),如图2-2所示。
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_2.jpg?sign=1739006817-va2TDuNhZkX2pbO9cQ0y0RmCPbVEbxfj-0-a1e0858fec66b89789130a6efc38516b)
图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的创建过程:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_3.jpg?sign=1739006817-Wz7DzwyG8GN20HFj2q87XU08W1QgCgK8-0-f29eb671360c24a0388f4d77f821c563)
以上代码可以总结为,当MediaPlayer通过create的方式创建播放器时,内部new出MediaPlayer对象,并setDataSource,做好prepare的动作。这时外部只需调用start函数,就能播放音视频资源了。
实例化MediaPlayer有如下两种方式。
(1)可以使用直接new的方式:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_4.jpg?sign=1739006817-WziMmIxbUzDDjrmPfYazhRfgm8p0Q4Nv-0-00e86b3933791ae52ecc3ee479f05e85)
(2)也可以使用create的方式,如:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_5.jpg?sign=1739006817-2Rm07wBubNn37kM3ZxaNXFyHWyhmEyv4-0-2c74d12c5aeb0b5b6de62e90e5625134)
上面两种实例化MediaPlayer的方式,都要经过new MediaPlayer,下面看看构造中做了什么操作。
播放页处理主页面:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_6.jpg?sign=1739006817-iHA9w4FcJUJmypmQcjx9ZPke5l2uerZR-0-b53e7ac320125e2c25fab4f271cdca4f)
接下来看Native层如何创建一个MediaPlayer。在介绍native_setup之前,请注意一般都是在静态代码块中加载.so文件的,在MediaPlayer中有一段静态代码块,用于加载和链接库文件media_jni.so,早于构造函数,在加载类时就执行。一般全局性的数据、变量都可以放在这里。下面是加载和链接media_jni.so文件的代码:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_7.jpg?sign=1739006817-bWVZ61YHtGU8vKLKduCDHAivXohVDWYF-0-ccf167b957a79cdefebe1c868d7cee79)
下面开始进入android_media_MediaPlayer.cpp分析,第一个函数android_media_Media-Player_native_init就是从Java静态代码块调过来的native_init:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_8.jpg?sign=1739006817-G5IuVLYeGq1dTHQMAiA7EQ2qe15Prmms-0-4ba9cde163d1e9ccd27812521beb8877)
上面这种方式是通过JNI调用Java层的MediaPlayer类,然后拿到mNativeContext的指针,接着调用了MediaPlayer.java中的静态方法postEventFromNative,把Native的事件回调到Java层,使用EventHandler post事件回到主线程中,用软引用指向原生的MediaPlayer,以保证Native代码是安全的。代码如下:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_9.jpg?sign=1739006817-4FSV3dHj3hXsi85ir4CmwgAiR2g1YM5U-0-d21e71585ca2e710c69e45962b427c4b)
之前我们在Java层的MediaPlayer.java文件的构造函数中,分析到最后有一个native_setup,在android_media_MediaPlayer.cpp中找到对应的函数,代码如下:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_11.jpg?sign=1739006817-LhAQsqJvTPrM7dszvnOoUTQNZckWZb8b-0-5734d6b2259472a9f78a43d95a708a25)
可以看到会设置一些回调用的listener及创建C++中的MediaPlayer对象。
2.2.3 setDataSource过程
上面就是MediaPlayer的构造过程。构造后接下来要设置数据源,进而到了setDataSource过程,下面看看setDataSource做了什么操作:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_12.jpg?sign=1739006817-lPZVVum03uc47sFZUrFWrjiYJnH5lEqP-0-55cb2e773a58e4d8443ce6b3aebb8af9)
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_13.jpg?sign=1739006817-az1Q9SYFojgpTHUrjrmva2v7JE0hKpUT-0-6e3414fd773bb6e0b49ffce38c1a5989)
先看看setDataSource中传入的参数是文件描述符的情况:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_14.jpg?sign=1739006817-wAORu9Igt1TYKrdJu7bPXaOLCyonuFpc-0-32fff90169d9f649b110950b8d298e8a)
开始进入JNI层,发现找不到android_media_MediaPlayer_setDataSource函数,但发现有一个函数名映射函数声明,这是JNI中常用的动态注册方法,代码如下:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_16.jpg?sign=1739006817-W9CsE8waE63umy56jeCyJOf2eTBKmhwn-0-54e6e0c341be653118b35ea0009d3f27)
对以上这个函数名映射,如果读者看过JNIEnv * 源码的话,应该不会感到陌生,无非还是映射,不影响我们的分析。在这里接下来对android_media_MediaPlayer_setDataSourceFD函数进行分析:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_17.jpg?sign=1739006817-iDjyWr6MuLlhQ7Idx538vO76EDWwFTFP-0-ba3e2bb3e98a6b7e5e7ec032f1568ce8)
接着分析process_media_player_call函数:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_18.jpg?sign=1739006817-dugX6QQ4WFDktQgmWtC2JAR3wVerPNFF-0-60f1ea9611c5aed90f30d926139f6b10)
总结以上代码:当mp->setDataSource(fd, offset, length)函数得到状态后,对各种状态进行通知。有异常的直接抛出,这样也就不会影响MediaPlayer后面的执行过程了。
接下来看看以HTTP/RTSP传入JNI。在Java层中对应的nativeSetDataSource函数如下:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_19.jpg?sign=1739006817-aLrEiNUJ81JphDOJR7GEyFvFSuTJVk03-0-fc3ee2c8db81dc61c30c33b00b9bb402)
在JNI中通过映射表可对应到android_media_MediaPlayer_setDataSourceAndHeaders函数:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_20.jpg?sign=1739006817-3w30Gi5D3AvHvgrsFfCrgXDz3FGTdGD7-0-78555cdd59352e3753cf6e4b234c0ee3)
至此,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):
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_22.jpg?sign=1739006817-BlYoEHfyizrcFWmTSCsPFZjOpM7lYnbU-0-371a7a4eb486dbb7037dc03b69a96f68)
对于上面代码中的第2点,同样在android_media_MediaPlayer.cpp中找到其对应的函数:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_24.jpg?sign=1739006817-A4BxYDT9ljAf8QYQl2RzKrQMOwrtgHER-0-c6518d91ab2fedd69750f54e44d92b39)
这里有如下几个概念需要理解。
• 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,时时做好更新准备。