ijkplayer read_thread命令简单解析

stream_open函数中,初始化完视频,音频,字幕的帧队列后,启动了两个线程

  • video_refresh_thread:刷新视频帧线程
  • read_thread:读取本地磁盘或者网络视频资源

read_thread流程

  1. 调用avformat_alloc_context
    • 创建AVFormatContext对象,主要为函数指针赋值,确定默认打开文件的函数,以及关闭文件的函数
  2. 调用avformat_open_input
    • 调用init_input:打开文件,探测视频格式
    • 调用avio_skip:跳过初始化的字节
    • 调用ff_id3v2_read_dict:判断是否有id3v2格式的字段
    • 调用s->iformat->read_header:从buffer里面读取视频头,确定解码器
    • 调用ff_id3v2_parse_chapters:解析id3v2中的章节、描述等信息
  3. 调用avformat_find_stream_info:解析视频中的各个流的信息,如video、audio流
  4. 调用avformat_seek_file:判断是否有seek操作,需要seek文件
  5. 调用avformat_match_stream_specifier:分离audio,video流信息,判断当前流的类型
  6. 调用av_find_best_stream:根据当前ffplayer找到video,audio,subtitle的流(一般的视频各个流都只有一个)
  7. 调用stream_component_open:打开audio,video的流信息,根据流信息找到decoder,然后开启各自的线程进行解码
  8. 调用ffp_notify_msg1发送FFP_MSG_PREPARED消息
  9. 进入无限循环,从解析流的线程中获取Packet,同步到video_refresh_thread线程中,进行时钟同步,开始播放
    • 判断是否有seek操作is->seek_req,若有则调用avformat_seek_file
    • 判断是否当前video、audio、subtitle的PacketQueue已满,如果已经满了,则直接进入下一次循环
    • 判断当前视频是否暂停或者播放完成,判断是否为循环播放,以及循环播放次数,重新seek到start_time的位置,若未设置,默认为0
    • 调用av_read_frame读取packet帧
    • 将对应的packet放到对应的video、audio、subtitle的PacketQueue
    • 判断ffp->packet_buffering,如果是的话,则调用ffp_check_buffering检查Buffer

read_thread主要函数分析

  1. avformat_alloc_context分析
  • 创建AVFormatContext对象
  • s->io_open赋值为io_open_default,之后文件打开用该函数指针指向的函数打开文件
static void avformat_get_context_defaults(AVFormatContext *s)
{
    ...
    s->io_open  = io_open_default;
    s->io_close = io_close_default;
    ...  
}

AVFormatContext *avformat_alloc_context(void)
{
    AVFormatContext *ic;
    ic = av_malloc(sizeof(AVFormatContext));
    if (!ic) return ic;
    avformat_get_context_defaults(ic);
    ...
    return ic;
}
  1. avformat_open_input分析

init_input函数中会通过s->io_open打开文件,而在avformat_alloc_context初始化AVFormatContext的时候,将io_open_default函数指针赋值给了s->io_open。所以如果没有修改的话,则使用该函数打开文件。

在该函数(io_open_default)中:

  1. 根据文件名找到对应的protocol。如Http,Tcp,Rtsp等
  2. 通过对应protocol的url_open2打开链接
  3. 解析出protocol以及hostname
  4. 替换http协议为tcp协议,到tcp.c中的tcp_open
  5. 根据DNS寻找域名缓存
  6. 如果没找到,则判断如果支持PTHREAD,则开启线程通过getAddressInfo获取域名
int avformat_open_input(AVFormatContext **ps, const char *filename,
                        AVInputFormat *fmt, AVDictionary **options)
{
    ...
    // 打开文件IO,并且探测文件格式
    if ((ret = init_input(s, filename, &tmp)) < 0)
        goto fail;
    // 探测结果赋值
    s->probe_score = ret;
    ...
    // 跳过字节数
    avio_skip(s->pb, s->skip_initial_bytes);
    ...
    /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */
    if (s->pb){  
        // 读取文件中的id3v2字段的值
        ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);
    }
    ...
    // 从buffer中读取文件头,确定文件头正确
    if (!(s->flags&AVFMT_FLAG_PRIV_OPT)) {
        if (s->iformat->read_header2) {
            if (options)
                av_dict_copy(&tmp2, *options, 0);
            if ((ret = s->iformat->read_header2(s, &tmp2)) < 0)
                goto fail;
        } else if (s->iformat->read_header && (ret = s->iformat->read_header(s)) < 0)
            goto fail;
    }
    ...
    // 解析文件id3v2中的apic以及chapter
    if (id3v2_extra_meta) {
        if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||
            !strcmp(s->iformat->name, "tta")) {
            if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0)
                goto fail;
            if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0)
                goto fail;
        } else
            av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n");
    }
    ff_id3v2_free_extra_meta(&id3v2_extra_meta);
    if ((ret = avformat_queue_attached_pictures(s)) < 0)
        goto fail;
    ...
    return ret;
}
  1. stream_component_open分析
/* open a given stream. Return 0 if OK */
static int stream_component_open(FFPlayer *ffp, int stream_index)
{
    ...
    //  根据stream_index找到对应的AVCodec
    codec = avcodec_find_decoder(avctx->codec_id);

    switch (avctx->codec_type) {
        case AVMEDIA_TYPE_AUDIO   : is->last_audio_stream    = stream_index; forced_codec_name = ffp->audio_codec_name; break;
        case AVMEDIA_TYPE_SUBTITLE: is->last_subtitle_stream = stream_index; forced_codec_name = ffp->subtitle_codec_name; break;
        case AVMEDIA_TYPE_VIDEO   : is->last_video_stream    = stream_index; forced_codec_name = ffp->video_codec_name; break;
        default: break;
    }
    //  根据forced_codec_name构造AVCodec
    if (forced_codec_name)
        codec = avcodec_find_decoder_by_name(forced_codec_name);
    ...
    avctx->codec_id = codec->id;
    ...
    ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;
    switch (avctx->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        ...
        /* prepare audio output */
        //  打开audio
        if ((ret = audio_open(ffp, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0)
            goto fail;
        ...
        //  初始化audio的解码器
        decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);
       ...
        //  开启线程执行audio_thread开始audio解码
        if ((ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec")) < 0)
            goto out;
        SDL_AoutPauseAudio(ffp->aout, 0);
        break;
    case AVMEDIA_TYPE_VIDEO:
        ...
        //  初始化视频的解码器
        decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);
        //  打开Pipeline的视频解码器
        ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);  
        //  启动video_thread线程开始视频解码
        if ((ret = decoder_start(&is->viddec, video_thread, ffp, "ff_video_dec")) < 0)
            goto out;
       ...
        break;
    case AVMEDIA_TYPE_SUBTITLE:
       ...
       ffp_set_subtitle_codec_info(ffp, AVCODEC_MODULE_NAME, avcodec_get_name(avctx->codec_id));
       decoder_init(&is->subdec, avctx, &is->subtitleq, is->continue_read_thread);
        if ((ret = decoder_start(&is->subdec, subtitle_thread, ffp, "ff_subtitle_dec")) < 0)
            goto out;
       ...
       return ret;
}

结论

在read_thread的过程中,发现比较耗时的地方只有三个:

  • s->io_open
  • s->iformat->read_header
  • SDL_Delay(20)

这三个地方总共耗时加起来大概已经180ms左右,所以需要针对这三个过程进行优化。至此,ijkplayer的prepared过程结束。在video_thread,audio_thread等解码完成后,会将解码完成的数据包同步到video_refresh_thread线程中进行时钟同步,同步完后,则会开始绘制第一帧。此时视频开始播放。

你可能感兴趣的:(FFmpeg)