这篇博客主要讲一下音视频开发过程中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 sp1.2 setDataSource过程(设置数据源)mp=new MediaPlayer(); 80 //给 MediaPlayer设置监听以便产生回调 81 sp listener=new JNIMediaPlayerListener(env,thiz,weak_this); 82 mp->setListener(listener); 83 //在JAVA层,C++中的程序是封装起来的,不透明的。 84 setMediaPlayer(env,thiz,mp); 85 }
这里边主要涉及到路径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.Entry entry: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 sp httpService; 179 if(httpServiceBinderObj!=NULL){ 180 sp binder=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 sp surface(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 sp mp=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架构部分。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)