ijkplayer初始化流程

本文记录的是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(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初始化流程)