zoukankan      html  css  js  c++  java
  • Android MediaPlayer架构 -- MediaPlayer的创建过程

    本文系作者自己学习之所用,文章内容仅出自作者拙劣之思考,问题之处烦请不吝指教。

      MediaPlayer 能被用来控制音/视频文件或流媒体的回放。Android中以MediaPlayer类作为音视频播放的基础类,围绕着他开展了一系列的处理。学习一个新的模块,最简单的步骤就是找到一个典型的应用程序,通过它的实现,来分析整个模块的数据流和控制流。典型的MediaPlayer在Java处的接口包括视频播放类VideoView以及音频专用MediaPlayer类。

      一、 一个简单的视频播放demo app

      Android中实现视频的播放可以采用MediaPlayer+SurfaceView配合的方式,其实Android还为开发人员提供了另外一种更简单的播放视频媒体的方式,那就是VideoView。VideoView类,其实质是用MediaPlayer类来实现的,只是由于其是视频播放,不得不和Surfaceview挂上够,才将其独立出来。使得其有如下的结构:

    1 public class VideoView extends SurfaceView
    2         implements MediaPlayerControl, SubtitleController.Anchor {
    3     private static final String TAG = "VideoView";
    4     ......
    5 }

      在Android中,提供了VideoView组件用于播放视频文件。想要使用VideoView组件播放视频,首先需要在布局文件中创建该组件,然后在Activity中获取该组件,并应用其setVideoPath()方法或setVideoURI()方法加载要播放的视频,最后调用start()方法来播放视频。另外,VideoView组件还提供了stop()和pause()方法,用于停止或暂停视频的播放。

      在APP中,VideoView的典型简单使用如下:

    1     mMediaController =new MediaController(this);
    2     mVideoView = (VideoView) findViewById(R.id.videoView); 
    3     mVideoView.setVideoPath("/sdcard/1080P24FPS.mp4"); // 设置档案路径
    4     mVideoView.setMediaController(mMediaController); // 设置播放器的控制器
    5     mVideoView.start(); // 开始播放

      先看看效果就是下面这个样子,短短几行代码一个播放器就做好了,还可以进行暂停,快进,快退,进度条控制。

      PS:VideoView还提供许多其他播放控制API,在此不做重点介绍,以上代码也仅仅是个人demo,难免有误,谨慎参考使用。

    二、 VideoView中setVideoPath的处理

      任何华丽的语言都不如source code来的简单直接,上代码:

     1     /**
     2      * Sets video path.
     3      *
     4      * @param path the path of the video.
     5      */
     6     public void setVideoPath(String path) {
     7         setVideoURI(Uri.parse(path));
     8     }
     9 
    10     /**
    11      * Sets video URI.
    12      *
    13      * @param uri the URI of the video.
    14      */
    15     public void setVideoURI(Uri uri) {
    16         setVideoURI(uri, null);
    17     }
    18 
    19     /**
    20      * Sets video URI using specific headers.
    21      *
    22      * @param uri     the URI of the video.
    23      * @param headers the headers for the URI request.
    24      *                Note that the cross domain redirection is allowed by default, but that can be
    25      *                changed with key/value pairs through the headers parameter with
    26      *                "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
    27      *                to disallow or allow cross domain redirection.
    28      */
    29     public void setVideoURI(Uri uri, Map<String, String> headers) {
    30         mUri = uri;
    31         mHeaders = headers;
    32         mSeekWhenPrepared = 0;
    33         openVideo(); // openVideo的处理,让最终的处理权交给了MediaPlayer
    34         requestLayout();
    35         invalidate();
    36     }

      经过setVideoPath(String path) --> setVideoURI(Uri uri) --> setVideoURI(Uri uri, Map<String, String> headers) 的调用流程,程序最终来到了openVideo()这一函数中:

     1     private void openVideo() {
     2             ......
     3             mMediaPlayer = new MediaPlayer();
     4 
     5             mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
     6             mMediaPlayer.setDisplay(mSurfaceHolder);
     7             
     8             mMediaPlayer.prepareAsync();
     9             .......
    10     }

      在上面的代码中可以清楚的看到,我们首先new 了一个MediaPlayer类的对象,然后去setDataSource,到这里VideoView::openVideo的处理让最终的处理权交给了MediaPlayer。接下来我们就进入MediaPlayer的世界.

    三、 MediaPlayer的世界

      3.1 new MediaPlayer对象过程

      首先关注MediaPlayer对象的创建过程,这也是分析android源码的一个基本要求。依次通过Java --> JNI(libmedia_jni.so) -- > Frameworks(libmedia.so)的处理流程。

      MediaPlayer.java 构造函数,这一部分在 Android MediaPlayer架构 -- 前言小知识点(一)也有分析

        public MediaPlayer() {
            super(new AudioAttributes.Builder().build());
    
            Looper looper;
            if ((looper = Looper.myLooper()) != null) {
                mEventHandler = new EventHandler(this, looper);
            } else if ((looper = Looper.getMainLooper()) != null) {
                mEventHandler = new EventHandler(this, looper);
            } else {
                mEventHandler = null;
            }
    
            mTimeProvider = new TimeProvider(this);
            mOpenSubtitleSources = new Vector<InputStream>();
    
            /* Native setup requires a weak reference to our object.
             * It's easier to create it here than in C++.
             */
            native_setup(new WeakReference<MediaPlayer>(this));
        }

      可以看到,在使用VideoView中到创建MediaPlayer会经过:new VideoView——> new MediaPlayer ——>native_setup 这样一个典型的对象建立过程,并传递到JNI。

      native_setup主要用于本地C++层的对象的建立,在JNI代码(frameworksasemediajniandroid_media_MediaPlayer.cpp)中可以找到对应的native函数:

     1 static void
     2 android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
     3 {
     4     ALOGV("native_setup");
     5     sp<MediaPlayer> mp = new MediaPlayer(); // 实例化一个native MediaPlayer(frameworksavmedialibmediamediaplayer.cpp)
     6     if (mp == NULL) {
     7         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
     8         return;
     9     }
    10 
    11     // create new listener and give it to MediaPlayer
    12     sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
    13     mp->setListener(listener);
    14 
    15     // Stow our new C++ MediaPlayer in an opaque field in the Java object.
    16     setMediaPlayer(env, thiz, mp);
    17 }

      进入JNI做android_media_MediaPlayer_native_setup处理:sp<MediaPlayer> mp = new MediaPlayer() 这个native MediaPlayer会去和media service进行交互实现真正的播放功能,使得最终处理进入C++的世界。   

      3.2 setDataSource过程

      MediaPlayer java class中提供了多种setDataSource方法来设置不同的URI播放流,在此我们以播放本地档案为例来介绍处理流程:

      VideoView::setVideoURI() --> MediaPlayer::setDataSource(mContext, mUri, mHeaders); --> MediaPlayer::setDataSource(uri.toString()) --> MediaPlayer::setDataSource(path, null, null) --> MediaPlayer::setDataSource(fd) --> setDataSource(fd, 0, 0x7ffffffffffffffL) --> _setDataSource(fd, offset, length)

      最后会调到 _setDataSource(fd, offset, length),看这个方法被声明为 native method

    1     private native void _setDataSource(MediaDataSource dataSource)
    2           throws IllegalArgumentException, IllegalStateException;

      在JNI层我们找到该方法对应的JNI method实现:

    1     {"_setDataSource",      "(Ljava/io/FileDescriptor;JJ)V",    (void *)android_media_MediaPlayer_setDataSourceFD},

        android_media_MediaPlayer_setDataSourceFD()方法定义如下:

    static void
    android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
    {
        sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
        if (mp == NULL ) {
            jniThrowException(env, "java/lang/IllegalStateException", NULL);
            return;
        }
    
        if (fileDescriptor == NULL) {
            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
            return;
        }
        int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
        ALOGV("setDataSourceFD: fd %d", fd);
        process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
    }

      上面这段代码可以看到最终调用了status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length)

    //*********************************************************************************************************************************************************************

      MediaPlayer的C++代码位于/frameworks/av/media/libmedia/mediaplayer.cpp, 编译后形成一个libmedia.so。

      下面来看这个API的处理,接下去都只分析framework层的C++的处理流

     1 status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length)
     2 {
     3     ALOGV("setDataSource(%d, %" PRId64 ", %" PRId64 ")", fd, offset, length);
     4     status_t err = UNKNOWN_ERROR;
     5     const sp<IMediaPlayerService>& service(getMediaPlayerService());
     6     if (service != 0) {
     7         sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
     8         if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
     9             (NO_ERROR != player->setDataSource(fd, offset, length))) {
    10             player.clear();
    11         }
    12         err = attachNewPlayer(player);
    13     }
    14     return err;
    15 }

      典型的Binder C/S架构,获取MediaPlayerService的proxy,通过MediaPlayerService来创建一个player,然后对这个player调用setDataSource

      3.3 MediaPlayerService的工作

      启动与获取

      MediaPlayerService同其他的Binder Service一样,作为一个server对外提供服务,它是在mediaserver中启动的:

      /frameworks/av/media/mediaserver/main_mediaserver.cpp

     1 int main(int argc __unused, char **argv __unused)
     2 {
     3     signal(SIGPIPE, SIG_IGN);
     4 
     5     sp<ProcessState> proc(ProcessState::self());
     6     sp<IServiceManager> sm(defaultServiceManager());
     7     ALOGI("ServiceManager: %p", sm.get());
     8     InitializeIcuOrDie();
     9     MediaPlayerService::instantiate(); //启动MediaPlayerService
    10     ResourceManagerService::instantiate();
    11     registerExtensions();
    12     ProcessState::self()->startThreadPool();
    13     IPCThreadState::self()->joinThreadPool();
    14 }

       在/frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp中对instantiate()方法的定义:

    1 void MediaPlayerService::instantiate() {
    2     defaultServiceManager()->addService(
    3             String16("media.player"), new MediaPlayerService());
    4 }

       在上面这段代码中我们注册了一个名为“media.player"的Binder Service,也就是MediaPlayerService,之后就可以通过 binder = sm->getService(String16("media.player"));来请求这个服务了

       Player的创建

      获取MediaPlayerService后就要去create player: sp<IMediaPlayer> player(service->create(this, mAudioSessionId));

      create请求处理:

      

  • 相关阅读:
    PhpStorm 常用快捷键和配置+关闭快捷键ctrl+alt+方向键旋转屏幕+快速复制一行快捷键恢复
    WP七牛云插件详解
    注册表删除键值时拒绝访问
    删除注册表子项清除u盘使用痕迹
    一件代发发货人怎么写?淘宝代理发货流程
    联动设置
    使用vue实现行列转换的一种方法。
    从后端到前端之Vue(五)小试路由
    从后端到前端之Vue(四)小试牛刀——真实项目的应用(树、tab、数据列表和分页)
    从后端到前端之Vue(三)小结以及一颗真实的大树
  • 原文地址:https://www.cnblogs.com/roger-yu/p/8144536.html
Copyright © 2011-2022 走看看