ijkplayer学习笔记(四)——消息循环启动

本篇涉及核心代码

ijkmp_prepare_async

在iOS层,调用了prepareToPlay方法

- (void)prepareToPlay
{
    ......
    ijkmp_prepare_async(_mediaPlayer);
}

深入跟进,ijkplayer内部最终会调用到ffplay.c中的

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)

启动播放器的入口函数,在此会设置player选项,打开audio output,最重要的是调用stream_open方法。,还有ijkmp_prepare_async_l中的

mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");

也是相当重要的部分,是上一篇media_player_msg_loop的创建入口,启动了消息循环(msg_loop)线程。

ffp_prepare_async_l

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
     ......
    /* 判断直播视频流协议rtmp或rtsp */
    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);
    }

    /* AVM格式中有长度限制*/
    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:";
        }
    }
    // 开启音频输出
    if (!ffp->aout) {
        ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp);
        if (!ffp->aout)
            return -1;
    }

    ......
    /*核心部分*/
    VideoState *is = stream_open(ffp, file_name, NULL);
    .......
    return 0;
}

stream_open

根据地址打开视频流,详见代码注释。

static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
{
     ......
    /* 创建3个队列 */
    /* 视频队列初始化: 解码前(videoq)解码后(pictq)*/
    if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)
        goto fail;
    /* 字幕队列初始化:解码前(subtitleq)解码后(subpq)*/
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
        goto fail;
    /* 音频队列初始化:解码前(audioq)解码后(sampq)*/
    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;
    ......
    /* 创建视频渲染线程video_refresh_thread*/
    is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");
   
    ......
    /* 创建数据读取线程*/
    is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");
    ......
}

read_thread(数据读取)

1、创建上下文结构体(表示输入上下文),用于数据填充

ic = avformat_alloc_context();

2、读取网络数据包的信息,探测流的协议,如http还是rtmp等。

err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);

3、探测媒体类型,可得到当前文件的封装格式,音视频编码参数等信息

err = avformat_find_stream_info(ic, opts);

4、核心函数stream_component_open,开启视频、音频解码器,创建相应的解码线程。后面第五篇将深入学习

    /* open the streams */
    if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
        stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]);
    } else {
        ffp->av_sync_type = AV_SYNC_VIDEO_MASTER;
        is->av_sync_type  = ffp->av_sync_type;
    }
    ret = -1;
    if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
        ret = stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO]);
    }
    if (is->show_mode == SHOW_MODE_NONE)
        is->show_mode = ret >= 0 ? SHOW_MODE_VIDEO : SHOW_MODE_RDFT;
    if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) {
        stream_component_open(ffp, st_index[AVMEDIA_TYPE_SUBTITLE]);
    }

5、开始无限循环,不断获取待播放的数据,调用ffmpeg的av_read_frame()读取AVPacket,然后加入队列。

   for (;;) {
        //AVPacket pkt;
        //进入循环,读取packet
        ret = av_read_frame(ic, pkt);
        //读取packet之后,根据packet类型分到不同的packetQueue里面去
        if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
            packet_queue_put(&is->audioq, pkt);
        } else if (pkt->stream_index == is->video_stream && pkt_in_play_range
                   && !(is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC))) {
            packet_queue_put(&is->videoq, pkt); stream_component_open
        } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
            packet_queue_put(&is->subtitleq, pkt);
        } else {
            av_packet_unref(pkt);
        }
    }  

video_refresh_thread视频渲染线程

iOS平台上采用OpenGL渲染解码后的YUV图像,渲染线程为video_refresh_thread

static int video_refresh_thread(void *arg)
{
    ......
    // 不断的显示下一帧
    while (!is->abort_request) {
        if (remaining_time > 0.0)
    // 如果距离下一帧还有时间,就用av_usleep先暂停会
            av_usleep((int)(int64_t)(remaining_time * 1000000.0));
        remaining_time = REFRESH_RATE;
        if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
            video_refresh(ffp, &remaining_time);
    }
   ......
}

参考:https://blog.csdn.net/xipiaoyouzi/article/details/74280170

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 笔记可能微乱,但大致清晰,可能会对他人有所帮助,故分享出来。 ×××××××××××××××××××目录×××××...
    王英豪阅读 6,210评论 0 8
  • 随着互联网技术的飞速发展,移动端播放视频的需求如日中天,由此也催生了一批开源/闭源的播放器,但是无论这个播放器功能...
    金山视频云阅读 46,644评论 28 170
  • 一、总体说明 1.打开ijkplayer,可看到其主要目录结构如下: tool - 初始化项目工程脚本 confi...
    laixh阅读 2,248评论 0 0
  • 本文主要针对B站开源播放器IJKPlayer的部分源码阅读笔记,包括Java代码和C代码,涉及到部分FFmpeg和...
    骆驼骑士阅读 939评论 0 0
  • 本文记录的是ijkplayer的初始化流程(重点在分析底层c代码的逻辑),为了更好的理解这部分内容,建议大家下载i...
    ce0b74704937阅读 3,148评论 0 1