JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

IjkPlayer 源码分析(一)--启动流程

wys521 2024-11-01 15:15:40 精选教程 34 ℃ 0 评论

一、前言

本文重点分析native 层的流程,对于业务层的流程可直接参考ijkplayer-example 中VideoActivity.java文件

二、初始化

2.1 so 加载及jni方法动态绑定

上层业务加载完ijkplayer.so / ijksdl.so/ijkffmpeg.so 之后会执行一次jni接口的绑定,jni方法列表如下(ijkplayer_jni.c):


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 },
    { "_setFrameAtTime",        "(Ljava/lang/String;JJII)V", (void *) IjkMediaPlayer_setFrameAtTime },
    { "setParameter",        "(IJJJ)V", (void *) NioMediaPlayer_setParameter },
    { "getParameter",        "(I)J", (void *) NioMediaPlayer_getParameter },
};


JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    JNIEnv* env = NULL;
    //jni 版本判断
    g_jvm = vm;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    
    pthread_mutex_init(&g_clazz.mutex, NULL );

    // FindClass returns LocalReference
    IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER);
    //进行jni 接口的动态绑定
    (*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );

    ijkmp_global_init();
    ijkmp_global_set_inject_callback(inject_callback);

    FFmpegApi_global_init(env);

    return JNI_VERSION_1_4;
}

2.2、初始化ffmpeg 相关

ijkplayer 是在ffplay 上做的二次封装它的播放,动态方法绑定之后 会进行ffmpeg 相关的初始化工作ijkmp_global_init() -> ijkplayer.c (ijkmp_global_init()) -> (ff_ffplay.c)ffp_global_init()

ff_ffplay.c (ffp_global_init() ) 函数如下:


void ffp_global_init()
{
    if (g_ffmpeg_global_inited)
        return;

    ALOGD("ijkmediaplayer version : %s", ijkmp_version());
    /* 1.register all codecs, demux and protocols */
    avcodec_register_all();
#if CONFIG_AVDEVICE
    //2、用于注册输入/输出设备,属于libavdevice模块,是libavformat模块的补充,
    //包含muxers与demuxers两部分。设备与平台相关,比如Android、iOS、Windows、Mac、Linux等不同平台提供不同设备
    //avdevice_register_all();
#endif
#if CONFIG_AVFILTER
    //3. 注册所有filter器 滤镜
    avfilter_register_all();
#endif
    //4. 注册所有文件格式和编解码库
    //即注册所有文件格式打包解包器
    av_register_all();
    // ijk 层的初始化  主要是 初始化一些支持的协议与  解复用
    ijkav_register_all();
    //4. 初始化网络相关参数
    //默认状态下 , FFMPEG 是不允许联网的 , 必须调用该函数 , 
    //初始化网络后 FFMPEG 才能进行联网
    avformat_network_init();
    
    //注册一个运行时锁
    //使用多个线程同时播放多个视频源的时候,在调用avcodec_open/close的时候,
    //可能导致失败,这个可以查阅ffmpeg的源码分析其中的原因,
    //失败的主要原因是在调用此2函数时,ffmpeg为了确保该2函数为原子操作,
    //在avcodec_open/close两函数的开头和结尾处使用了一个变量entangled_thread_counter
    //来记录当前函数是否已经有其他线程进入,如果有其他线程正在此2函数内运行,则会调用失败
    av_lockmgr_register(lockmgr);
    //查看ffmpeg的日志信息,可使用如下函数
    av_log_set_callback(ffp_log_callback_brief);
    //初始化缓冲队列
    av_init_packet(&flush_pkt);
    flush_pkt.data = (uint8_t *)&flush_pkt;

    g_ffmpeg_global_inited = true;
}

3.以上2步 在加载so 时即会完成之后 业务层会执行native_setup() 方法:


IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
    MPTRACE("%s\n", __func__);
    //创建IjkMediaPlayer 播放实例,
    //需要注意的是ijkmp_android_create的入参是一个函数指针,
    //这里的传入的是message_loop
    // 创建Native层的IjkMediaPlayer对象,并且将message_loop对象赋与mp->msg_loop
    IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
    JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError", "mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);
    
    //将播放器实例 在native 层做一次绑定,方便其他接口调用,将mp 赋值给了 结构体
    // 通过JNI将上步创建的IjkMediaPlayer对象的指针地址保存到Java层的mNativeMediaPlayer属性中
    // 并且释放旧的IjkMediaPlayer
    // J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer 成员变量
    // jfieldID field_mNativeMediaPlayer
    // 文件在 :IjkMediaPlayer.c 
    // 此处需要特别注意的是 J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer
    // 实例的构造是通过宏进行类加载的
    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函数在文件ijkplayer_android.c 中,源码如下:


这里传入了函数message_loop,函数里面的内容后面分析,可以看到,ijkmp_create的入参也是一个函数指针,文件在 :ijkplayer.c


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;
}

这里面的流程我们也比较熟悉了,首先是去底层创建FFmpeg,之后会将上面传下来的msg_loop赋值给IjkMediaPlayer结构体维护的函数指针变量msg_loop。进入ffp_create函数看一下跟消息队列相关的内容:

FFPlayer *ffp_create()
{
    av_log(NULL, AV_LOG_INFO, "av_version_info: %s\n", av_version_info());
    av_log(NULL, AV_LOG_INFO, "ijk_version_info: %s\n", ijk_version_info());

    /* 1.申请FFPlayer结构体内存并初始化为0 */
    FFPlayer* ffp = (FFPlayer*) av_mallocz(sizeof(FFPlayer));
    if (!ffp)
        return NULL;
    /* 2.初始化ffp的消息队列msg_queue */
    msg_queue_init(&ffp->msg_queue);
    ffp->af_mutex = SDL_CreateMutex();
    ffp->vf_mutex = SDL_CreateMutex();
    /* 3.对FFPlayer结构体成员进行reset操作 */
    ffp_reset_internal(ffp);
    ffp->av_class = &ffp_context_class;
    ffp->meta = ijkmeta_create();

    av_opt_set_defaults(ffp);

    las_stat_init(&ffp->las_player_statistic);
    return ffp;
}

初始化各个结构体的关联图:

三、播放阶段

1.native_setup执行完,业务层通过setDataSource 设置文件地址,这个地址最终会传给native 层。

setDataSource 最终会调用到ijkmp_set_data_source_l

  • 将原来的mp->data_source指针释放
  • 重新将url生成char *赋值给mp->data_source
  • 将ijkmediaplayer对象的状态修改成MP_STATE_INITIALIZED
static int ijkmp_set_data_source_l(IjkMediaPlayer *mp, const char *url)
{
    assert(mp);
    assert(url);

    // MPST_RET_IF_EQ(mp->mp_state, MP_STATE_IDLE);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_INITIALIZED);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_ASYNC_PREPARING);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_PREPARED);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_STARTED);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_PAUSED);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_COMPLETED);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_STOPPED);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_ERROR);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_END);

    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;
}

2.接着进入prepareAsync阶段最终也会调用到ijkmp_prepare_async_l

  • 将ijkmediaplayer的状态修改为MP_STATE_ASYNC_PREPARING
  • 初始化消息队列&mp->ffplayer->msg_queue
  • 初始化消息处理线程,线程处理function为ijk_msg_loop
  • 调用ffp_prepare_async_l调用ffmpeg中的方法开始prepare
static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)
{
    ...
    ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING);
    // 向&mp->ffplayer->msg_queue中丢入一个FFP_MSG_FLUSH消息
    // 最终目的只是初始化Native的MessageQueue
    msg_queue_start(&mp->ffplayer->msg_queue);

    // released in msg_loop
    ijkmp_inc_ref(mp);
    // 创建处理消息队列的线程,
    // 向Java层IjkMediaPlayer的postEventFromNative回调
    // 该回调会回调到EventHandler中,而队列中只有FFP_MSG_FLUSH这个消息,这只是一个NOP的测试消息
    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;
}

ffp_prepare_async_l中真正调用ffmpeg开始准备播放

  • 判断url协议是否为rtmp或者rtsp,如果是则取消timeout参数
  • 如果url长度大于1024,则加入ijklongurl参数
  • 调用stream_open打开视频流,使用FFPlayer播放
int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
    ...
    if (av_stristart(file_name, "rtmp", NULL) ||
        av_stristart(file_name, "rtsp", NULL)) {
        // There is total different meaning for 'timeout' option in rtmp
        av_log(ffp, AV_LOG_WARNING, "remove 'timeout' option for rtmp.\n");
        av_dict_set(&ffp->format_opts, "timeout", NULL, 0);
    }

    /* there is a length limit in avformat */
    if (strlen(file_name) + 1 > 1024) {
        av_log(ffp, AV_LOG_ERROR, "%s too long url\n", __func__);
        if (avio_find_protocol_name("ijklongurl:")) {
            av_dict_set(&ffp->format_opts, "ijklongurl-url", file_name, 0);
            file_name = "ijklongurl:";
        }
    }
    ...
    av_opt_set_dict(ffp, &ffp->player_opts);
    if (!ffp->aout) {
        ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp);
        if (!ffp->aout)
            return -1;
    }

#if CONFIG_AVFILTER
    if (ffp->vfilter0) {
        GROW_ARRAY(ffp->vfilters_list, ffp->nb_vfilters);
        ffp->vfilters_list[ffp->nb_vfilters - 1] = ffp->vfilter0;
    }
#endif
    //  打开视频流
    VideoState *is = stream_open(ffp, file_name, NULL);
    if (!is) {
        av_log(NULL, AV_LOG_WARNING, "ffp_prepare_async_l: stream_open failed OOM");
        return EIJK_OUT_OF_MEMORY;
    }

    ffp->is = is;
    ffp->input_filename = av_strdup(file_name);
    return 0;
}
  • stream_open开始打开视频流,其中VideoState也就代表着视频当前的状态,包括帧,数据,解码器等等 初始化帧队列:&is->pictq&is->subpq&is->sampq,Queue大小为16
  • 初始化数据包队列:&is->videoq&is->audioq&is->subtitleq
  • 初始化时针:&is->vidclk&is->audclk&is->extclk,代表当前视频video,audio的时刻
  • 创建视频刷新的线程is->video_refresh_tid
  • 创建视频读取线程is->read_tid
  • 调用decoder_init初始化视频解码器
static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
{
    VideoState *is;  //  视频内容以及状态
    ...
    /* start video display */
    if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)
        goto fail;
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
        goto fail;
    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
        goto fail;

    if (packet_queue_init(&is->videoq) < 0 ||
        packet_queue_init(&is->audioq) < 0 ||
        packet_queue_init(&is->subtitleq) < 0)
        goto fail;

    if (!(is->continue_read_thread = SDL_CreateCond())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        goto fail;
    }

    if (!(is->video_accurate_seek_cond = SDL_CreateCond())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        ffp->enable_accurate_seek = 0;
    }

    if (!(is->audio_accurate_seek_cond = SDL_CreateCond())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        ffp->enable_accurate_seek = 0;
    }

    init_clock(&is->vidclk, &is->videoq.serial);
    init_clock(&is->audclk, &is->audioq.serial);
    init_clock(&is->extclk, &is->extclk.serial);
    is->audio_clock_serial = -1;
    ...
    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->initialized_decoder = 0;
    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());
        goto fail;
    }

    if (ffp->async_init_decoder && !ffp->video_disable && ffp->video_mime_type && strlen(ffp->video_mime_type) > 0
                    && ffp->mediacodec_default_name && strlen(ffp->mediacodec_default_name) > 0) {
        if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2) {
            decoder_init(&is->viddec, NULL, &is->videoq, is->continue_read_thread);
            ffp->node_vdec = ffpipeline_init_video_decoder(ffp->pipeline, ffp);
        }
    }
    is->initialized_decoder = 1;

    return is;
fail:
    is->initialized_decoder = 1;
    is->abort_request = true;
    if (is->video_refresh_tid)
        SDL_WaitThread(is->video_refresh_tid, NULL);
    stream_close(ffp);
    return NULL;
}

整体的播放结构流程图:

附:状态图

状态涵义:

四、参考文档

https://www.jianshu.com/p/42a583c5e968

https://blog.csdn.net/fanyun_01/article/details/121630725

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表