![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=1739547639-2wlpLrv08xdUhiiFSDr9RKTcvOvCVj81-0-d8d4fc91a674b7cc72b9e07acce426cf)
图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=1739547639-AIsHD7aTD791BvcKWk1BQCcZW7b0KHFH-0-597f750aee2049e665e91bc20deda316)
以上代码可以总结为,当MediaPlayer通过create的方式创建播放器时,内部new出MediaPlayer对象,并setDataSource,做好prepare的动作。这时外部只需调用start函数,就能播放音视频资源了。
实例化MediaPlayer有如下两种方式。
(1)可以使用直接new的方式:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_4.jpg?sign=1739547639-z7TmoEZ7nhyXVQfUgp6gmhEsJg8K1xwM-0-6006611b48593cad920066d4cbc21b1f)
(2)也可以使用create的方式,如:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_5.jpg?sign=1739547639-ZCUxpuUXeeHZS6iqNZHh9SEmHGLgjiAA-0-508f95a8795bc8c3f478feac0c505ced)
上面两种实例化MediaPlayer的方式,都要经过new MediaPlayer,下面看看构造中做了什么操作。
播放页处理主页面:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_6.jpg?sign=1739547639-UrKWECuBYsfY8zIgsdv8CRYET7kZZQWK-0-9472c8c9c00bbe9a4255ac1fa02dc494)
接下来看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=1739547639-pKTiRAlasgfkrIWCaQiis6EpTq8TBIjS-0-5da0848ec466280f00628c711ee73e00)
下面开始进入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=1739547639-BGTKOetIuda3RRn6DwzzdJMSiqRDlnMa-0-897ef3c558c044b4e26f75e88fb22f11)
上面这种方式是通过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=1739547639-K5CcA9yWIZhbMWhsUHJOzrgA9FOeIG9w-0-1c3c05459e6a3f78f6575e830c2e2704)
之前我们在Java层的MediaPlayer.java文件的构造函数中,分析到最后有一个native_setup,在android_media_MediaPlayer.cpp中找到对应的函数,代码如下:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_11.jpg?sign=1739547639-tE4prPrqNwasdeH4M2JfvQOGsHzBmpvK-0-343010b32ef1b3787b7e624274c7aab6)
可以看到会设置一些回调用的listener及创建C++中的MediaPlayer对象。
2.2.3 setDataSource过程
上面就是MediaPlayer的构造过程。构造后接下来要设置数据源,进而到了setDataSource过程,下面看看setDataSource做了什么操作:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_12.jpg?sign=1739547639-oTMZLvzCH51h9LhPQlvouqLZF3HieS8Z-0-76b2841256ede60332a4ce2a3c91307d)
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_13.jpg?sign=1739547639-SdFuWjMLTW0r9vGdtmIVqxUBSFviEekV-0-42c85f0e031a6f86540590f0a6ff28ac)
先看看setDataSource中传入的参数是文件描述符的情况:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_14.jpg?sign=1739547639-3p7kcJOq6In66un9UWSKKbXwEcJkCOCC-0-339a660f83b624078198ba60494f0bea)
开始进入JNI层,发现找不到android_media_MediaPlayer_setDataSource函数,但发现有一个函数名映射函数声明,这是JNI中常用的动态注册方法,代码如下:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_16.jpg?sign=1739547639-2wYfvR9ZfPD5CY5DltHEiKlFtADTtpu6-0-108f4b0cd2ab830ee272ca818897b662)
对以上这个函数名映射,如果读者看过JNIEnv * 源码的话,应该不会感到陌生,无非还是映射,不影响我们的分析。在这里接下来对android_media_MediaPlayer_setDataSourceFD函数进行分析:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_17.jpg?sign=1739547639-1RDgd67SJRG7CtFGIKYLe7DEIwnRzqsG-0-9ad3afeba9148998c49694ea274020a6)
接着分析process_media_player_call函数:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_18.jpg?sign=1739547639-dav78czLSDVkbToFb7sG18wgRl11aeWi-0-b68cbfa79417fb29d0d8a728cf856d05)
总结以上代码:当mp->setDataSource(fd, offset, length)函数得到状态后,对各种状态进行通知。有异常的直接抛出,这样也就不会影响MediaPlayer后面的执行过程了。
接下来看看以HTTP/RTSP传入JNI。在Java层中对应的nativeSetDataSource函数如下:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_19.jpg?sign=1739547639-u6VzN5sQ1qe0sKRLfwqa1IqAuPaC16pM-0-0d45b2939009ba4b128c768cf89310ca)
在JNI中通过映射表可对应到android_media_MediaPlayer_setDataSourceAndHeaders函数:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_20.jpg?sign=1739547639-q7OK0uwC6JRhMBxlCeGN6zuVRmkUcZ3k-0-8c71c33e0eb3e6faa1bf4a53f1819a15)
至此,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=1739547639-FZC4CU8GJAvEinm4m3x2fNkWGlJfXwsf-0-eea893a55a9c148000667e5b5587ec00)
对于上面代码中的第2点,同样在android_media_MediaPlayer.cpp中找到其对应的函数:
![](https://epubservercos.yuewen.com/13E7E8/16896237204360306/epubprivate/OEBPS/Images/txt002_24.jpg?sign=1739547639-BorcvBlcmS6qJPaSOidUZBwNV6bbf3OR-0-b6e999f5b51b0b958760d48ac0a392a6)
这里有如下几个概念需要理解。
• 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,时时做好更新准备。