这篇博客主要讲一下音视频开发过程中MediaPlayer的一整个创建以及运行的过程,主要以代码进行详解。
1.从创建到setDisplay的过程 1.1创建过程当外部调用MediaPlayer.create(this,“ ”)时,以下是其创建过程的代码:
3 public static MediaPlayer creat(Context context,Uri uri,SurfaceHolder
4 holder,AudioAttributes audioAttributes,int audioSessionId)
5 {
6 try{
7 MediaPlayer mp=new MediaPlayer();//新建一个MediaPlayer
8 final AudioAttributes aa=audioAttributes!=null?audioAttributes:new
AudioAttributes.Builder().build();
9 //声音相关处理,若为空,则创建一个新的
10 mp.setAudioAttributes(aa);//设置音频属性
11 mp.setAudioSessionId(audioSessionId);//设置声音的会话ID,视频和音频是分开渲染的
12 mp.setDataSource(context,uri);//从这里开始setDataSource.其中uri为统一资源标识符
13 if(holder!=null){//判断SurfaceHolder是否为空
14 //用来操纵Surface,处理在Canvas上作画的效果和动画,控制表面、大小、像素等
15 mp.setDisplay(holder);//gei Surface设置一个Surface的控制器
16 }
17 mp.prepare();//开始准备
18 return mp;
19 }
20 catch(IOException ex){
21 //IOException ex为异常类型,异常处理代码,这里省略操作
22 }
23 return null;
24 }
new MediaPlayer构造的具体实现代码如下:(播放页处理主页面)
public MediaPlayer(){
27 Looper looper;//定义一个Looper
28 if((looper=Looper.myLooper()1=null)){//若myLooper不为空就赋值到looper
29 mEventHandler=new EventHandler(this,looper);//实例化一个EventHandler对象
30 }else if((looper=Looper.getMainLooper())!=null){
//如果主线程Looper不为空,也可赋值到looper
31 }else{
32 mEventHandler=null;
33 }
34 mTimeProvider=new mTimeProvider(this);
//时间数据容器,provider和数据联系起来,如VideoProvider等
35 mOpenSubtitleSources=new Vector();//通过Binder机制获取系统原生服务,像Camera、MediaRecorder、MediaPlayer等服务都会申请> ,用于打开摄像头、获取声音等
36 IBinder b=ServiceManager.getService(Context.APP_OPS_SERVICE);
37 mAppOps=IAppOpsService.Stub.asInterface(b);//Binder的服务连接桥,进行IPC通信,下面使用native_setup开始创建,此处为软引用
38 native_setup(new WeakReference(this));
39 }
MediaPlayer构造函数总结:
1.定义Looper
2.初始化TimeProvider
3.通过Binder获取原生ops服务
4.进入C++层创建一个弱引用的MediaPlayer
Native层在创建一个MediaPlayer之前需要加载和链接media_jni.so文件,在加载类的时候执行。
41 static{
42 System.loadLibrary("media_jni");//media_jni.so
43 native_init();
44 }
native创建MediaPlayer中,第一个函数native_init是通过JNI调用java层的MediaPlayer类。
46 static void android_media_MediaPlayer_native_init(JNIEnv *env){
47 //这里理解成一个万能指针,通过(->)访问JNI中的函数
48 jclass clazz;//类的句柄
49 clazz=env->FindClass("android/media/MediaPlayer");
50 //这里通过Native层调用Java层,获取MediaPlayer对象
51 if(clazz==NULL){
52 //找不到退出
53 return;
54 }
55 fields.context=env->GetFieldID(clazz,"mNativaContext","J");
56 //获取成员变量mNativeContext,它在MediaPlayer.java中是一个long型整数,实际对应的是一个内存地址
57 if(fields.context==NULL){
58 return;
59 }
60 fields.post_event=env->GetStaticMethodId(clazz,"postEventFromNative","(Ljava/lang/Object;IIILjava/lang/Object;)V");
61 if(fields.post_event==NULL){
62 return;
63 }
64
65 }
将Native事件回调到Java层,使用EventHandle post事件回到主线程中,利用软引用指向原生的MediaPlayer,保证Native代码是安全的。
66 private static void postEventFromNative(Object mediaplayer_ref,int what,int arg1,int
arg2,Object obj){
67 mediaplayer mp=(MEdiaPlayer)((WeakRefrence)mediaplayer_ref).get();//得到弱引用对象
68 if(mp==null){
69 return;
70 }
71 if(mp.mEventHandler!=null){
72 //handler不为空,就继续发送一条msg
73 Message m=mp.mEventHandle.obtainMessage(what,arg1,arg2,obj);
74 mp.mEventHandler.sendMessage(m);
75 }
76 }
在JAVA层MediaPlayer文件中的构造函数中,有一个native_setup,在android_media_MediaPlayer.cpp的对应函数如下:
78 static void android_media_MediaPlayer_native_setup(JNIEnv *env,jobject thiz,jobject
weak_this){
79 spmp=new MediaPlayer();
80 //给 MediaPlayer设置监听以便产生回调
81 splistener=new JNIMediaPlayerListener(env,thiz,weak_this);
82 mp->setListener(listener);
83 //在JAVA层,C++中的程序是封装起来的,不透明的。
84 setMediaPlayer(env,thiz,mp);
85 }
1.2 setDataSource过程(设置数据源)
这里边主要涉及到路径path或者HTTP/RTSP地址,利用Map将对应的keys和values键值对装入2个数组中,发送到setDataSource进行文件或非文件的处理。本代码主要以解析uri本地文件资源形式的代码,看具体代码分析如下:
66 public void setDataSource(String path)
67 throws
IOException,IllegalArgumentException,SecurityException,IllegalStateException{
68 setDataSource(path,null,null);
69 }//setDataSource包含文件路径或HTTP、RTSP地址
70 public void setDataSource(String path,Mapheaders) throws
IOException,IllegalArgumentException,SecurityException,IllegalStateException{
71 String[] keys=null;
72 String[] values=null;
73 if(headers!=null){
74 keys=new String[headers.size()];//请求头部对应的key
75 values=new String[headers.size()];//请求头部对应的values
76 int i;
77 //把HTTP/RTSP中包含的key、value分别装到两个数组中
78 for(Map.Entryentry:headers.entrySet()){
79 keys[i]=entry.getKey();
80 values[i]=entry.getValue();
81 ++i;
82 }
83 }
84 //将key和value送入setDataSource
85 setDataSource(path,keys,values);
86 }
87 private void setDataSource(String path,String[] keys,String[]values) throws
IOException,IllegalArgumentException,SecurityException,IllegalStateException{
88 final Uri uri=Uri.parse(path);//解析path
89 final String scheme=uri.getScheme();
90 if("file".equles(scheme)){
91 path=uri.getPath();
92 }else if(scheme!=null){
93 //1.处理非文件资源
94 nativeSetDataSource
(MediaHttpService.creatHttpServiceBinderIFNecessary(path),path,keys,values);
95 return;
96 }
97 //2.处理文件类型
98 final File file=new File(path);
99 if(file.exists()){
100 FileInputStream is=new FileInputStream(file);
101 FileDescriptor fd=is.getFD();//得到文件描述符fd
102 setDataSource(fd);
103 is.close();
104 }else{
105 throw new IOException("setDataSource failed.");
106 }
107 }
上述代码中的setDataSource(fd)具体的传入参数文件描述符fd的函数如下:
109 public void setDataSource(FileDescriptor fd) throws
IOException,IllegalArgumentException,SecurityException,IllegalStateException{
110 setDataSource(fd,0,0x7ffffffffffffffL);
111 }
112
113 private void setDataSource(FileDescriptor fd,long offset,long length) throws
IOException,IllegalArgumentException,SecurityException,IllegalStateException{
114 _setDataSource(fd,offset,length);
115 }
116 private native void _setDataSource(FileDescriptor fd,long offset,long length)throws
IOException,IllegalArgumentException,SecurityException,IllegalStateException;
setDataSource是一个正向过程,从java->JNI->C++。接下来开始进入JNI层,JNI层中找不到setDataSource函数,但有一个函数名映射函数声明,是JNI中常用的动态注册方法。
118 static JNINativeMethod GMethods[]={
119 {
120 "nativeSetDataSource",
121 "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;"
122 "[Ljava/lang/String;)V",
123 (void*)android_media_MediaPlayer_setDataSourceAndHeaders
124 },
125 {"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V",
126 (void*)android_media_MediaPlayer_setDataSourceFD},
127 {"_setDataSource", "(Landroid/meida/MeidaDataSource;)V",
128 (void*)android_media_MediaPlayer_setDataSourceCallback},
129
130 {"_setVideoSurface", "(Landroid/view/Surface;)V",
131 (void*)android_media_MediaPlayer_setVideoSurface},
132 //相关函数名的映射native函数声明
133 };
接下来,我们对上述函数名映射函数声明中的setDataSourceFD函数进行代码分析:
132 static void
133 android_media_MediaPlayer_setDataSourceFD(JNIEnv *env,jobject thiz,jobject
fileDescriptor, jlong offset,jlong length)
134 {
135 spmp=getMediaPlayer(env,thiz);//得到MediaPlayer对象
136 int fd=jniGetFDFromFileDescriptor(env,fileDescriptor);
137 //在JNI中获取java.io.FileDescriptor
138 ALOGV("setDataSourceFD:fd %d",fd);
139 process_media_player_call(env,thiz,mp-
>setDataSource(fd,offset,length),"java/io/IOException","setDataSourceFD failed.");
140 }
141 //这里开始调用JNIEnv*中的GetIntField函数获取对应的变量
142 int jniGetFDFromFileDescriptor(JNIEnv* env,jobject fileDescriptor){
143 return (*env)->GetIntField(env,fileDescriptor,gCachedFields.descriptorField);
144 }
对process_meida_player_call函数进行代码分析:
146 static void process_media_player_call(JNIEnv *env,jobject thiz,status_t
opStatus,const char* exception,const char *message)
147 {
148 if(exception==NULL){
149 //不抛出异常,发送一个onError事件
150 if(opStatus!=(status_t) OK){
151 //若在setDataSource过程中opStatus 不 OK
152 spmp=getMediaPlayer(env,thiz);
153 if(mp!=0)mp->notify(MEDIA_ERROR,opStatus,0);
154 //通知 MEDIA_ERROR
155 }
156 }
157 //此处省略抛出异常的部分代码
158 }
当mp->setDataSource(fd,offset,length)函数得到状态后,对各种状态进行通知,有异常的直接抛出。
上述都是以本地文件路径path进行传入JNI的,接下来以HTTP/RTSP传入JNI。
在java层中对应的nativeSetDataSource函数如下:
160 private native void nativeSetDataSource(IBinder httpServiceBinder,String 161 path,String[]keys,String[] values)throws IOException,IllegalArgumentException, 162 SecurityException,IllegalStateException;
在JNI中通过映射表找到android_media_MediaPlayer_setDataSourceAndHeaders函数:
163 static void
164 android_media_MediaPlayer_setDataSourceAndHeaders(JNIEnv *env,jobject thiz,jobject
httpServiceBinderObj,jstring path,jobjectArray keys,jobjectArray values){
165 //获取MediaPlayer对象
166 spmp=getMediaPlayer(env,thiz);
167 //省略判空,抛出异常代码
168 const char *tmp=env->GetStringUTFChars(path,NULL);
169 if(tmp==NULL){
170 //内存溢出
171 return;
172 }
173 ALOGV("setDataSource:path %s",tmp);
174 String8 pathStr(tmp);
175 env->ReleaseStringUTFChars(path,tmp);
176 tmp=NULL;
177 //省略部分代码
178 sphttpService;
179 if(httpServiceBinderObj!=NULL){
180 spbinder=ibinderForJavaObject(env,httpServiceBinderObj);
181 //通过Binder机制将httpServiceBinderObj传给IPC并返回给binder
182 //然后强制转换成IMediaHTTPService
183 httpService=interface_cast(binder);
184 }
185 //开始判断状态,和上面的文件操作一样
186 status_t opStatus=
187 mp->setDataSource(
188 httpService,
189 pathStr,
190 headersVector.size()>0?&headersVector:NULL);
191 //见上面的文件操作
192 process_media_player_call(
193 env,thiz,opStatus,"java/io/IOException","setDataSource failed.");
194 }
以上就是setDataSource数据源设置的过程,从java->JNI->C++是正向调用过程,反之是回调过程。
总结一下setDataSource的主要功能:
1.拿到uri,本地或者HTTP的。判断uri北荣是否为文件资源
2.若为文件资源,检查fd文件描述符,判断类型,检查完成之后mp->setDataSource()
3.若为HTTP/RTSP网络请求,则通过Binder做IPC网络通信,完成之后mp->setDataSource()
4.通过JNI,JAVA和C++相互调用
具有安全性、效率高、连通性强的特点。
1.3 setDisplay()过程mp.setDisplay(holder):
holder容器,那SurfaceHolder控制着每一个Surface。定义SurfaceHolder如下:
196 public void setDisplay(SurfaceHolder sh){
197 mSurfaceHolder=sh;//1.给Surface设置一个控制器,用于展示视频图像
198 Surface surface;
199 if(sh!=null){
200 surface=sh.getSurface();
201 }else{
202 surface=null;
203 }
204 _setVideoSurface(surface);//2.给视频设置Surface,带_的函数是native函数
205 updateSurfaceScreenOn();//3.更新Surface到屏幕上
206 }
在native层的CPP文件中找到相对应的函数如下:
214 static void
215 setVideoSurface(JNIEnv *env,jobject thiz,jobject jsurface,jboolean
mediaPlayerMustBeAlive)
216 {
217 spmp=getMediaPlayer(env,thiz);
218 //此处省略抛出异常的代码
219 decVideoSurfaceRef(env,thiz);
220 //IGraphicBufferProducer
221 sp new_st;
222 if(jsurface){
223 //得到JAVA层的Surface
224 spsurface(android_view_Surface_getSurface(env,jsurface));
225 if(surface!=NULL){
226 //不为空,获取IGraphiBufferProducer
227 new_st=surface->getIGraphicBufferProducer();
228 //省略抛出异常的代码
229 //调用incStrong
230 new_st->incStrong((void*)decVideoSurfaceRef);
231 }else{
232 //省略抛出异常的代码
233 return;
234 }
235 }
236 env->SetLongField(thiz,fields.surface_texture,(jlong)new_st.get());
237 //如果MediaPlayer还未被初始化,setDataSource将失败,但setDataSource之前就setDisplay
了,在prepare/prepareAsync中调用setVideoSurfaceTexture可以覆盖该case
238 mp->setVideoSurfaceTexture(new_st);
239 }
240
241 static void
242 decVideoSurfaceRef(JNIEnv *env,jobject thiz)
243 {
244 spmp=getMediaPlayer(env,thiz);
245 //省略部分代码
246 //得到旧的SurfaceTexture
247 sp old_st=getVideoSurfaceTexture(env,thiz);
248 if(old_st!=NULL){
249 old_st->decStrong((void*)decVideoSurfaceRef);
250 }
251 }
上述代码中涉及到几个特殊的类。在这里进行解释说明一下:
1.SurfaceTexture:是Android3.0加入的类,它主要功能是从视频解码里面获取图像流image stream,它接收之后不需要显示出来。所以,我们可以将其副本进行处理,处理完毕之后送给另一个SurfaceView用于显示。
2.Surface:初始被屏幕排序的原生的Buffer,其实就是一个画图象的地方,且都是画在Surface上,各个Surface通过SurfaceFlinger合成到freamBuffer。每个Surface都是双缓冲的(双线程),一个是渲染线程,一个是UI更新线程。它有一个bacBuffer和frontBuffer。
3.SurfaceView:被用来显示图像。可以控制Surface的格式和尺寸以及位置。Surface是管理数据的地方。SurfaceView是展示数据的地方。
4.SurfaceHolder:是一个管理SurfaceHolder的容器,是Surface的监听器。通过回调函数addCallback(SurfaceHolder.Callback callback)监听Surface的创建。
5.IgraphicBufferProducer:图形缓冲的管理者,是APP和BufferQueue的交通桥梁,承担UI需求的显示。
总结setDisplay的过程:
其实就是将想要显示的视频进行参数预设置等。
1.SurfaceHolder容器给native层一个Surface,用来处理数据
2.native层处理完成之后,SurfaceHolde通知更新SurfaceView
今天先总结到这里,创建过程、setDataSource过程、setDisplay过程都已总结完毕,还有prepare过程没有进行详细总结,下个博客我们将总结prepare和C++中的MediaPlayer中C/S架构部分。



