美文网首页ijkplayer
ijkplayer初始化流程

ijkplayer初始化流程

作者: ce0b74704937 | 来源:发表于2018-05-29 22:01 被阅读0次

    本文记录的是ijkplayer的初始化流程(重点在分析底层c代码的逻辑),为了更好的理解这部分内容,建议大家下载ijk的源码,最好结合ijkplayer android端调试配置好环境,有利于查看底层c代码

    直接切入主题,因为要看初始化流程,直接看到VideoActivity.java/onCreate(),代码如下

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            ...
            // init player
            IjkMediaPlayer.loadLibrariesOnce(null);
            ...
            // prefer mVideoPath
            if (mVideoPath != null)
                mVideoView.setVideoPath(mVideoPath);
            else if (mVideoUri != null)
                mVideoView.setVideoURI(mVideoUri);
            else {
                ...
            }
            ...
        }
    

    从上面代码看到,VideoActivity.java/onCreate()函数主要做了两件事:
    1. IjkMediaPlayer.loadLibrariesOnce(null),加载库
    2. setVideoPath或者setVideoURI设置播放路径

    我们一个一个来仔细看这两步分别做了什么事
    1. IjkMediaPlayer.loadLibrariesOnce()
    代码如下

        public static void loadLibrariesOnce(IjkLibLoader libLoader) {
            synchronized (IjkMediaPlayer.class) {
                if (!mIsLibLoaded) {
                  if (libLoader == null)
                        libLoader = sLocalLibLoader;               
    
                    // libLoader.loadLibrary("ijkffmpeg");
                    libLoader.loadLibrary("ijksdl");
                    libLoader.loadLibrary("ijkplayer");
                    ...
                }
            }
        }
    

    可以看到该函数里面调用了loadLibrary函数加载底层库(我这里为什么注销libLoader.loadLibrary("ijkffmpeg")这一行与调试有关,具体查看ijkplayer android端调试),加载好库后,java是怎么能够调用c代码的呢?

    原来还有一个jni层的概念,在调用System.loadLibrary()函数时,会调用JNI_OnLoad,而IjkMediaPlayer.loadLibrariesOnce传入的是null,逻辑走libLoader=sLoadLibLoader,所以libLoader.loadLibrary最后就是调用的System.loadLibrary,此时就会调用ijkplayer_jni.c/JNI_OnLoad()函数

    我们继续看看ijkplayer_jni.c/JNI_OnLoad()干了些什么事,代码如下

    JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
    {
        ...
        (*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );
    
        ijkmp_global_init();
        ...
    }
    

    ijkplayer_jni.c/JNI_OnLoad()主要做了两件事:
    1) (*env)->RegisterNatives()该方法是为了注册g_methods中的native方法,其中有

    static JNINativeMethod g_methods[] = {
        {
            "_setDataSource",
            "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
            (void *) IjkMediaPlayer_setDataSourceAndHeaders
        },
        { "_setDataSourceFd",       "(I)V",     (void *) IjkMediaPlayer_setDataSourceFd },
        { "_setDataSource",         "(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V", (void *)IjkMediaPlayer_setDataSourceCallback },
        { "_setAndroidIOCallback",  "(Ltv/danmaku/ijk/media/player/misc/IAndroidIO;)V", (void *)IjkMediaPlayer_setAndroidIOCallback },
    
        { "_setVideoSurface",       "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },
        { "_prepareAsync",          "()V",      (void *) IjkMediaPlayer_prepareAsync },
        { "_start",                 "()V",      (void *) IjkMediaPlayer_start },
        { "_stop",                  "()V",      (void *) IjkMediaPlayer_stop },
        { "seekTo",                 "(J)V",     (void *) IjkMediaPlayer_seekTo },
        { "_pause",                 "()V",      (void *) IjkMediaPlayer_pause },
        { "isPlaying",              "()Z",      (void *) IjkMediaPlayer_isPlaying },
        { "getCurrentPosition",     "()J",      (void *) IjkMediaPlayer_getCurrentPosition },
        { "getDuration",            "()J",      (void *) IjkMediaPlayer_getDuration },
        { "_release",               "()V",      (void *) IjkMediaPlayer_release },
        { "_reset",                 "()V",      (void *) IjkMediaPlayer_reset },
        { "setVolume",              "(FF)V",    (void *) IjkMediaPlayer_setVolume },
        { "getAudioSessionId",      "()I",      (void *) IjkMediaPlayer_getAudioSessionId },
        { "native_init",            "()V",      (void *) IjkMediaPlayer_native_init },
        { "native_setup",           "(Ljava/lang/Object;)V", (void *) IjkMediaPlayer_native_setup },
        { "native_finalize",        "()V",      (void *) IjkMediaPlayer_native_finalize },
    
        { "_setOption",             "(ILjava/lang/String;Ljava/lang/String;)V", (void *) IjkMediaPlayer_setOption },
        { "_setOption",             "(ILjava/lang/String;J)V",                  (void *) IjkMediaPlayer_setOptionLong },
    
        { "_getColorFormatName",    "(I)Ljava/lang/String;",    (void *) IjkMediaPlayer_getColorFormatName },
        { "_getVideoCodecInfo",     "()Ljava/lang/String;",     (void *) IjkMediaPlayer_getVideoCodecInfo },
        { "_getAudioCodecInfo",     "()Ljava/lang/String;",     (void *) IjkMediaPlayer_getAudioCodecInfo },
        { "_getMediaMeta",          "()Landroid/os/Bundle;",    (void *) IjkMediaPlayer_getMediaMeta },
        { "_setLoopCount",          "(I)V",                     (void *) IjkMediaPlayer_setLoopCount },
        { "_getLoopCount",          "()I",                      (void *) IjkMediaPlayer_getLoopCount },
        { "_getPropertyFloat",      "(IF)F",                    (void *) ijkMediaPlayer_getPropertyFloat },
        { "_setPropertyFloat",      "(IF)V",                    (void *) ijkMediaPlayer_setPropertyFloat },
        { "_getPropertyLong",       "(IJ)J",                    (void *) ijkMediaPlayer_getPropertyLong },
        { "_setPropertyLong",       "(IJ)V",                    (void *) ijkMediaPlayer_setPropertyLong },
        { "_setStreamSelected",     "(IZ)V",                    (void *) ijkMediaPlayer_setStreamSelected },
    
        { "native_profileBegin",    "(Ljava/lang/String;)V",    (void *) IjkMediaPlayer_native_profileBegin },
        { "native_profileEnd",      "()V",                      (void *) IjkMediaPlayer_native_profileEnd },
    
        { "native_setLogLevel",     "(I)V",                     (void *) IjkMediaPlayer_native_setLogLevel },
        { "_injectCacheNode",       "(IJJJJ)V",                 (void *) IjkMediaPlayer_injectCacheNode },
    };
    

    这个类似数组的结构相当于是定义java层与c层之间的映射关系,比如在java层调用_prepareAsync函数,其实最终调用的是c层的IjkMediaPlayer_prepareAsync函数
    2) ijkplayer_jni.c/ijkmp_global_init()->ijkplayer.c/ffp_global_init()->ff_ffplayer.c/ffp_global_init(),可见最终调用的是ff_ffplayer.c/ffp_global_init(),函数实现如下

    void ffp_global_init()
    {
        ...
        /* register all codecs, demux and protocols */
        avcodec_register_all();  //注册所有编译好的编解码器
        ...
        av_register_all();  //注册所有编译好的封装器
        ...
        avformat_network_init();  //对一些网络功能进行初始化
        ...
    }
    

    其中ff_ffplayer.c/ffp_global_init()又主要做了三件事

    • avcodec_register_all(),注册所有编译好的编解码器
    • av_register_all(),注册所有编译好的封装器
    • avformat_network_init(),对一些网络功能进行初始化

    2. setVideoPath或者setVideoURI
    其实二者都是调用的IjkVideoView.java/setVideoURI()最后调用IjkVideoView.java/openVideo(),IjkVideoView.java/openVideo()代码如下

            private void openVideo() {
            if (mUri == null || mSurfaceHolder == null) {
                // not ready for playback just yet, will try again later
                return;
            }
            // we shouldn't clear the target state, because somebody might have
            // called start() previously
            release(false);
    
            AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
            am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
    
            try {
                mMediaPlayer = createPlayer(mSettings.getPlayer());
                ...
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                        mSettings.getUsingMediaDataSource() &&
                        (TextUtils.isEmpty(scheme) || scheme.equalsIgnoreCase("file"))) {
                    IMediaDataSource dataSource = new FileMediaDataSource(new File(mUri.toString()));
                    mMediaPlayer.setDataSource(dataSource);
                }  else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                    mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders);
                } else {
                    mMediaPlayer.setDataSource(mUri.toString());
                }
                ...
                mMediaPlayer.prepareAsync();
                ...
            } catch (IOException ex) {
                ...
            } catch (IllegalArgumentException ex) {
                ...
            } finally {
                // REMOVED: mPendingSubtitleTracks.clear();
            }
        }
    

    IjkVideoView.java/openVideo()又主要做了三件事:
    1) createPlayer,这里默认是创建IjkMediaPlayer(当然这里你也可以选择创建其他的播放器),最终调用IjkMediaPlayer.java/initPlayer()函数初始化播放器,初始化代码如下

       private void initPlayer(IjkLibLoader libLoader) {
            loadLibrariesOnce(libLoader);
            initNativeOnce();
    
            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;
            }
    
            /*
             * Native setup requires a weak reference to our object. It's easier to
             * create it here than in C++.
             */
            native_setup(new WeakReference<IjkMediaPlayer>(this));
        }
    

    a. loadLibrariesOnce之前解释过,也调用过
    b. initNativeOnce,然后调用IjkMediaPlayer.java/native_init(),其实就是调用IjkMediaPlayer_native_init(),这里没做什么操作好像
    c. 创建handler,用于后面c层代码调用java代码来发送message信息
    d. native_setup,其实就是调用ijkplayer_jni.c/IjkMediaPlayer_native_setup()函数,该函数里面又调用了ijkplayer_jni.c/ijkmp_android_create()

    IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
    {
        IjkMediaPlayer *mp = ijkmp_create(msg_loop);
        if (!mp)
            goto fail;
    
        mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();
        if (!mp->ffplayer->vout)
            goto fail;
    
        mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);
        if (!mp->ffplayer->pipeline)
            goto fail;
    
        ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout);
    
        return mp;
    
    fail:
        ijkmp_dec_ref_p(&mp);
        return NULL;
    }
    
    • ijkplayer_android.c/ijkmp_create()->ijkplayer.c/ffp_create()(这里可以看出ijk其实底层就是包装了一个ffplayer)创建播放器的时候还传入了一个msg_loop(ijkplayer_jni.c/message_loop_n),这个函数名是用于之后创建消息处理线程用的
    • ijkplayer_android.c/SDL_VoutAndroid_CreateForAndroidSurface(),创建图像渲染对象
    • ijkplayer_android.c/ffpipeline_create_from_android(),创建平台相关的IJKFF_Pipeline对象,包括视频解码以及音频输出部分

    2) mMediaPlayer.setDataSource用于设置视频源,其实是调用了jni层的ijkplayer_jni.c/IjkMediaPlayer_setDataSourceAndHeaders()函数,最终调用了ijkplayer.c/ijkmp_set_data_source_l()

    static int ijkmp_set_data_source_l(IjkMediaPlayer *mp, const char *url)
    {
        ...
        mp->data_source = strdup(url);
       ...
        ijkmp_change_state_l(mp, MP_STATE_INITIALIZED);
        return 0;
    }
    
    • mp->data_source = strdup(url)设置视频源
    • ijkmp_change_state_l(),将MP_STATE_INITIALIZED消息推入ffplayer->msg_queue中,就是ffplayer的消息队列

    3) mMediaPlayer.prepareAsync(),调用了ijkplayer_jni.c/ijkmp_prepare_async(),最终调用到ijkplayer.c/ijkmp_prepare_async_l()

    static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)
    {
        ...
        ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING);
    
        msg_queue_start(&mp->ffplayer->msg_queue);
    
        // released in msg_loop
        ijkmp_inc_ref(mp);
        mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
        // msg_thread is detached inside msg_loop
        // TODO: 9 release weak_thiz if pthread_create() failed;
    
        int retval = ffp_prepare_async_l(mp->ffplayer, mp->data_source);
        if (retval < 0) {
            ijkmp_change_state_l(mp, MP_STATE_ERROR);
            return retval;
        }
    
        return 0;
    }
    

    a ijkmp_change_state_l、msg_queue_start还是将消息推入ffplayer->msg_queue中
    b mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop")用来创建处理处理消息的线程,这里的ijkmp_msg_loop就是在之前jkplayer_android.c/ijkmp_create()赋值的
    c 在ffp_prepare_async_l()中

    • 先调用ff_ffplayer.c/ ffpipeline_open_audio_output(),ffpipeline_open_audio_output内部调用func_open_audio_output()该函数指针在前面的ijkplayer_android.c/ffpipeline_create_from_android()中赋值其实就是调用ff_ffpipeline.c/ func_open_audio_output()最终调用ff_ffpipeline_android.c/ func_open_audio_output()用于选择相应音频输出方式(opensles or audiotrack)
    • 调用ff_ffplayer.c/stream_open,其中stream_open()会创建读线程和视频渲染线程,这部分稍后的文章会详细讲解,只是这里稍微说一下在读线程read_thread()准备好后ffp->prepared = true将准备好的标志位置为true,ffp_notify_msg1(ffp, FFP_MSG_PREPARED)将准备好的消息推给消息队列,消息线程接到这个消息后调用java层的函数,向handler发送MEDIA_PREPARED,handler的handleMessage函数里面会处理这个消息
    public void handleMessage(Message msg) {
                ...
                switch (msg.what) {
                case MEDIA_PREPARED:
                    player.notifyOnPrepared();
                    return;
                ...
                default:
                    DebugLog.e(TAG, "Unknown message type " + msg.what);
                }
            }
        }
    

    最后调用下面函数,该函数又调用start(),即IjkMediaPlayer_start()函数开始视频的播放,到这里ijkplayer的初始化流程就结束了,内容很多,一定要下载代码多看几次

    public void onPrepared(IMediaPlayer mp) {
                mPrepareEndTime = System.currentTimeMillis();
                mHudViewHolder.updateLoadCost(mPrepareEndTime - mPrepareStartTime);
                mCurrentState = STATE_PREPARED;
    
                // Get the capabilities of the player for this stream
                // REMOVED: Metadata
    
                if (mOnPreparedListener != null) {
                    mOnPreparedListener.onPrepared(mMediaPlayer);
                }
                if (mMediaController != null) {
                    mMediaController.setEnabled(true);
                }
                mVideoWidth = mp.getVideoWidth();
                mVideoHeight = mp.getVideoHeight();
    
                int seekToPosition = mSeekWhenPrepared;  // mSeekWhenPrepared may be changed after seekTo() call
                if (seekToPosition != 0) {
                    seekTo(seekToPosition);
                }
                if (mVideoWidth != 0 && mVideoHeight != 0) {
                    //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
                    // REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight);
                    if (mRenderView != null) {
                        mRenderView.setVideoSize(mVideoWidth, mVideoHeight);
                        mRenderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
                        if (!mRenderView.shouldWaitForResize() || mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
                            // We didn't actually change the size (it was already at the size
                            // we need), so we won't get a "surface changed" callback, so
                            // start the video here instead of in the callback.
                            if (mTargetState == STATE_PLAYING) {
                                start();
                                if (mMediaController != null) {
                                    mMediaController.show();
                                }
                            } else if (!isPlaying() &&
                                    (seekToPosition != 0 || getCurrentPosition() > 0)) {
                                if (mMediaController != null) {
                                    // Show the media controls when we're paused into a video and make 'em stick.
                                    mMediaController.show(0);
                                }
                            }
                        }
                    }
                } else {
                    // We don't know the video size yet, but should start anyway.
                    // The video size might be reported to us later.
                    if (mTargetState == STATE_PLAYING) {
                        start();
                    }
                }
            }
    

    相关文章

      网友评论

        本文标题:ijkplayer初始化流程

        本文链接:https://www.haomeiwen.com/subject/tkoajftx.html