Android音视频开发(3)MediaPlayer运行过程

Android音视频开发(3)MediaPlayer运行过程,第1张

Android音视频开发(3)MediaPlayer运行过程

这篇博客主要讲一下音视频开发过程中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架构部分。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4751354.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-08
下一篇 2022-11-08

发表评论

登录后才能评论

评论列表(0条)

保存