欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ijkplayer 入门之 初始化

程序员文章站 2022-07-01 20:29:15
...

我们先将创建播放器、设置播放url、prepareAsync归为初始化部份
首先是创建播放器
java层代码

//创建播放器
 public IjkMediaPlayer() {
        this(sLocalLibLoader);
    }

 public IjkMediaPlayer(IjkLibLoader libLoader) {
        initPlayer(libLoader);
    }

    private void initPlayer(IjkLibLoader libLoader) {
        loadLibrariesOnce(libLoader);
        //就是调用了C的IjkMediaPlayer_native_init的方法,但没有什么操作
        initNativeOnce();
          ……
        native_setup(new WeakReference<IjkMediaPlayer>(this));
}

loadLibrariesOnce加载本地的so库,在加载JNI时,C代码会对应的调用了JNI_OnLoad,而卸载时会自动调用JNI_OnUnload
该函数在ijkplayer_jni.c

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    ……

    pthread_mutex_init(&g_clazz.mutex, NULL );

    // FindClass returns LocalReference
    IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER);
    (*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );

    ijkmp_global_init(); //主要是ffmpeg的初始化工作
    ijkmp_global_set_inject_callback(inject_callback);

    FFmpegApi_global_init(env);//初始化FFmpegApi

    return JNI_VERSION_1_4;
}

这里通过RegisterNatives注册相关native方法,在java层调用的C的函数均可以在g_methods里找得到,接着是一些ffmpeg的初始化工作
回到java层,接着调用了native_setup方法,我们在g_methods里找到了对应的函数IjkMediaPlayer_native_setup

static void
IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
    MPTRACE("%s\n", __func__);
    IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
    JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError", "mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);

    jni_set_media_player(env, thiz, mp);
    ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
    ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
    ijkmp_set_ijkio_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
    ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));

LABEL_RETURN:
    ijkmp_dec_ref_p(&mp);
}

ijkmp_android_create(message_loop),创建player对象,参数为函数指针,函数体为循环获取消息,这里最终与java层的handler消息机制对应着。
创建播放器的大致流程就到这里结束了,接着我们来看下seturl,最终调用的是setDataSourceFd方法,对应C中的

{ "_setDataSourceFd",       "(I)V",     (void *) IjkMediaPlayer_setDataSourceFd }

这里以设置网络文件为例(其中最终播放本地文件和网络文件的下层代码是一致的,只是前期逻辑有些区别)

static void
IjkMediaPlayer_setDataSourceFd(JNIEnv *env, jobject thiz, jint fd)
{
    MPTRACE("%s\n", "wsMediaPlayer_setDataSourceFd");
    int retval = 0;
    int dupFd = 0;
    char uri[128];
    IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
    JNI_CHECK_GOTO(fd > 0, env, "java/lang/IllegalArgumentException", "mpjni: setDataSourceFd: null fd", LABEL_RETURN);
    JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: setDataSourceFd: null mp", LABEL_RETURN);

    dupFd = dup(fd);

    ALOGV("setDataSourceFd: dup(%d)=%d\n", fd, dupFd);
    snprintf(uri, sizeof(uri), "pipe:%d", dupFd);
    retval = ijkmp_set_data_source(mp, uri);

    IJK_CHECK_MPRET_GOTO(retval, env, LABEL_RETURN);

LABEL_RETURN:
    ijkmp_dec_ref_p(&mp);
}

这个函数做的事不算多,最终所url设置进mp数据源,发送了一个消息MP_STATE_INITIALIZED,主要代码都可以通过ijkmp_set_data_source跟踪到

static int ijkmp_set_data_source_l(IjkMediaPlayer *mp, const char *url)
{
    ……
    freep((void**)&mp->data_source);
    mp->data_source = strdup(url);
    if (!mp->data_source)
        return EIJK_OUT_OF_MEMORY;

    ijkmp_change_state_l(mp, MP_STATE_INITIALIZED);
    return 0;
}

最后是prepareAsync,一样在C的注册方法中我可以找到

{ "_prepareAsync",          "()V",      (void *) IjkMediaPlayer_prepareAsync },

最终调用的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;
}

SDL_CreateThreadEx,创建了msg_loop消息队列的线程,
而在ffp_prepare_async_l里有个很重要的函数stream_open

VideoState *is = stream_open(ffp, file_name, NULL);

为什么说这个重要的,因为stream_open函数里创建了两个很重要的线程

 is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");
    if (!is->video_refresh_tid) {
        av_freep(&ffp->is);
        return NULL;
    }

    is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");
    if (!is->read_tid) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %s\n", SDL_GetError());

一个是视频渲染刷新线程video_refresh_thread,一个是数据读取线程read_thread,而在read_thread中获取到第一帧相关数据后就会向上层发送FFP_MSG_PREPARED信息,最终我们在java层OnPreparedListener接收到相关数据后就可以开始观看视频了

相关标签: ijkplayer