ijkplayer 代码走读之 播放器启动过程详解

上篇 ijkPlayer 代码走读之 Demoplayer 中,在 Android 代码层面是如何启动播放器,
我们已经说过,简单回顾一下,创建app应用时,

protected void onCreate(Bundle savedInstanceState) {
    
    
		mVideoView.setVideoPath();				    ///> 1. 设置视频源内容
		mVideoView.start();							///> 2. 启动播放器
}

mVideoView.setVideoPath(){
    
    
		mMediaPlayer = createPlayer();				///> 1.1 创建播放器 
		mMediaPlayer.prepareAsync();				///> 1.2 把播放器设置到预备态
}

简单的说呢,在建立用户app时,通过三步创建 ijkplayer,分别如下:

  1. 创建播放器 --> 2. 把播放器设置到预备态 --> 3. 启动播放器

ijkPlayer 的内核库是如何把建立播放器的呢,我们本次重点走读这方面代码。

///> 第 1 步 创建播放器,源码路径ijkmedia/ijkplayer/ff_ffplay.c  
IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void*))
{
    
    
    IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
    if (!mp)
        goto fail;

    mp->ffplayer = ffp_create();
    if (!mp->ffplayer)
        goto fail;

    mp->msg_loop = msg_loop;

    ijkmp_inc_ref(mp);
    pthread_mutex_init(&mp->mutex, NULL);

    return mp;

    fail:
    ijkmp_destroy_p(&mp);
    return NULL;
}

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());

    FFPlayer* ffp = (FFPlayer*) av_mallocz(sizeof(FFPlayer));
    if (!ffp)
        return NULL;

    msg_queue_init(&ffp->msg_queue);
    ffp->af_mutex = SDL_CreateMutex();
    ffp->vf_mutex = SDL_CreateMutex();

    ffp_reset_internal(ffp);
    ffp->av_class = &ffp_context_class;
    ffp->meta = ijkmeta_create();

    av_opt_set_defaults(ffp);

    return ffp;
}

IjkMediaMeta *ijkmeta_create()
{
    
    
    IjkMediaMeta *meta = (IjkMediaMeta *)calloc(1, sizeof(IjkMediaMeta));
    if (!meta)
        return NULL;

    meta->mutex = SDL_CreateMutex();
    if (!meta->mutex)
        goto fail;

    return meta;
fail:
    ijkmeta_destroy(meta);
    return NULL;
}
///> 以上程序总结为创建播放器的内存对象、再创建对应的锁。

///> 第 2 步 把播放器准备到预备状态,源码路径ijkmedia/ijkplayer/ff_ffplay.c  
int ijkmp_prepare_async(IjkMediaPlayer *mp)
{
    
    
    assert(mp);
    MPTRACE("ijkmp_prepare_async()\n");
    pthread_mutex_lock(&mp->mutex);
    int retval = ijkmp_prepare_async_l(mp);
    pthread_mutex_unlock(&mp->mutex);
    MPTRACE("ijkmp_prepare_async()=%d\n", retval);
    return retval;
}

static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)
{
    
    
    assert(mp);

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

    assert(mp->data_source);

    ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING);

    msg_queue_start(&mp->ffplayer->msg_queue);

    // released in msg_loop
    ijkmp_inc_ref(mp);     ///> 2.0 创建播放器控制队列,如播放器音量、屏幕亮度等
    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;
												///> 2.1 把 数据源 送入到 ffplayer 中。
    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;
}


int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
    
    
    assert(ffp);
    assert(!ffp->is);
    assert(file_name);

    if (av_stristart(file_name, "rtmp", NULL) ||
        av_stristart(file_name, "rtsp", NULL)) {
    
    	///> 检查协议类型是否 rtmp,rtsp 协议
        // 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) {
    
    				///> 检查url地址是否超长处理
        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);                     ///> 设置 扩充longURL 协议处理方法
            file_name = "ijklongurl:";
        }
    }

    av_log(NULL, AV_LOG_INFO, "===== versions =====\n");
    ffp_show_version_str(ffp, "ijkplayer",      ijk_version_info());
    ffp_show_version_str(ffp, "FFmpeg",         av_version_info());
    ffp_show_version_int(ffp, "libavutil",      avutil_version());
    ffp_show_version_int(ffp, "libavcodec",     avcodec_version());
    ffp_show_version_int(ffp, "libavformat",    avformat_version());
    ffp_show_version_int(ffp, "libswscale",     swscale_version());
    ffp_show_version_int(ffp, "libswresample",  swresample_version());
    av_log(NULL, AV_LOG_INFO, "===== options =====\n");
    ffp_show_dict(ffp, "player-opts", ffp->player_opts);
    ffp_show_dict(ffp, "format-opts", ffp->format_opts);
    ffp_show_dict(ffp, "codec-opts ", ffp->codec_opts);
    ffp_show_dict(ffp, "sws-opts   ", ffp->sws_dict);
    ffp_show_dict(ffp, "swr-opts   ", ffp->swr_opts);
    av_log(NULL, AV_LOG_INFO, "===================\n");

    av_opt_set_dict(ffp, &ffp->player_opts);					///> 设置 ffplayer 缺省参数
    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
										///> 2.2 开启视频播放器输入流
    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;
}

///> 播放器建立过程
static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
{
    
    
    assert(!ffp->is);
    VideoState *is;

    is = av_mallocz(sizeof(VideoState));
    if (!is)
        return NULL;
    is->filename = av_strdup(filename);
    if (!is->filename)
        goto fail;
    is->iformat = iformat;
    is->ytop    = 0;
    is->xleft   = 0;
#if defined(__ANDROID__)
    if (ffp->soundtouch_enable) {
    
    
        is->handle = ijk_soundtouch_create();
    }
#endif

    /* start video display */  ///> 2.3 建立视频流队列
    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;
																///> 2.4 创建 SDL-Cond 的 continue_read_thread() 线程
    if (!(is->continue_read_thread = SDL_CreateCond())) {
    
    						
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        goto fail;
    }
															///> 创建 SDL-Cond 的 video_accurate_seek_cond() 线程
    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;
    }
															 ///> 创建 SDL-Cond 的 audio_accurate_seek_cond() 线程
    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;
    if (ffp->startup_volume < 0)
        av_log(NULL, AV_LOG_WARNING, "-volume=%d < 0, setting to 0\n", ffp->startup_volume);
    if (ffp->startup_volume > 100)
        av_log(NULL, AV_LOG_WARNING, "-volume=%d > 100, setting to 100\n", ffp->startup_volume);
    ffp->startup_volume = av_clip(ffp->startup_volume, 0, 100);
    ffp->startup_volume = av_clip(SDL_MIX_MAXVOLUME * ffp->startup_volume / 100, 0, SDL_MIX_MAXVOLUME);
    is->audio_volume = ffp->startup_volume;
    is->muted = 0;
    is->av_sync_type = ffp->av_sync_type;

    is->play_mutex = SDL_CreateMutex();
    is->accurate_seek_mutex = SDL_CreateMutex();
    ffp->is = is;
    is->pause_req = !ffp->start_on_prepared;
												///> 2.5 创建 video_refresh_thread() 视频刷新线程
    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;	   ///> 2.6 创建 read_thread() 输入数据读取线程
    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) {
    
    
        		///> 2.7 初始化编码器
            decoder_init(&is->viddec, NULL, &is->videoq, is->continue_read_thread);		
          ///> 2.8 初始化编码器视频管道  
            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;
}

///> 把 播放器配置到 '预备态' 是创建播放器的核心步骤,我分别用 2.0 ~ 2.8 标号标注出来。这个过程是FFMPEG视频解码配置基本框架逻辑。

///> 第 3 步 ijkmedia/ijkplayer/ff_ffplay.c  启动把播放器状态改变为播放状态,通过SDL的事件方式通知给FFPlayer就okay了。
int ijkmp_start(IjkMediaPlayer *mp)
{
    
    
    assert(mp);
    MPTRACE("ijkmp_start()\n");
    pthread_mutex_lock(&mp->mutex);
    int retval = ijkmp_start_l(mp);
    pthread_mutex_unlock(&mp->mutex);
    MPTRACE("ijkmp_start()=%d\n", retval);
    return retval;
}

static int ijkmp_start_l(IjkMediaPlayer *mp)
{
    
    
    assert(mp);

    MP_RET_IF_FAILED(ikjmp_chkst_start_l(mp->mp_state));

    ffp_remove_msg(mp->ffplayer, FFP_REQ_START);
    ffp_remove_msg(mp->ffplayer, FFP_REQ_PAUSE);
    ffp_notify_msg1(mp->ffplayer, FFP_REQ_START);						///> 通知 ffplayer 播放音视频

    return 0;
}

static int ikjmp_chkst_start_l(int mp_state)
{
    
    
    MPST_RET_IF_EQ(mp_state, MP_STATE_IDLE);
    MPST_RET_IF_EQ(mp_state, MP_STATE_INITIALIZED);
    MPST_RET_IF_EQ(mp_state, MP_STATE_ASYNC_PREPARING);
    // MPST_RET_IF_EQ(mp_state, MP_STATE_PREPARED);
    // MPST_RET_IF_EQ(mp_state, MP_STATE_STARTED);
    // MPST_RET_IF_EQ(mp_state, MP_STATE_PAUSED);
    // MPST_RET_IF_EQ(mp_state, MP_STATE_COMPLETED);
    MPST_RET_IF_EQ(mp_state, MP_STATE_STOPPED);
    MPST_RET_IF_EQ(mp_state, MP_STATE_ERROR);
    MPST_RET_IF_EQ(mp_state, MP_STATE_END);

    return 0;
}

通过以上过程分析,我们基本清楚播放器是启动过程,对于不了解 FFMPEG 播放器的同学呢,还是
感觉播放器的工作流不是很清楚。
至此,我需要说明一下 ijkplayer 封装 ffplay 整体逻辑,首先我们要知道 ijkplayer是封装ffplay,
ffplay 是运行在so库的基础上,也就是说播发器实例是在linux用户空间程序,不是 Android 虚拟机中
运行的程序,播放器的执行效率的问题。
其次 ijkplayer 封装 android 系统主要工作是 JNI 在 ffmpeg 中增加部分功能,ffplay主体并没有
本质变化。
下一篇博文我们在看看 ffplay 工作流是如何运转的, 我们只重点走读 2.5、 2.6 和 2.8 部分代码,就能够
清晰 ffplay 的工作流. 其中 2.0 部分代码主要是 ffplay 控制接口线程,实现方法是通过SDL EVENT
机制来完成,此部分内容我们就不展开走读代码了。

猜你喜欢

转载自blog.csdn.net/weixin_38387929/article/details/121234024