前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >IjkPlayer数据读取-read_thread

IjkPlayer数据读取-read_thread

作者头像
None_Ling
发布2018-10-24 14:38:09
1.7K0
发布2018-10-24 14:38:09
举报
文章被收录于专栏:Android相关Android相关

前情回顾

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,之后文件打开用该函数指针指向的函数打开文件
代码语言:javascript
复制
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获取域名
代码语言:javascript
复制
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分析
代码语言:javascript
复制
/* 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线程中进行时钟同步,同步完后,则会开始绘制第一帧。此时视频开始播放。

参考资料

ijkplayer-android框架详解 ijkPlayer主流程分析 IjkPlayer播放器秒开优化以及常用Option设置 直播中的首屏加载优化

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.08.28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前情回顾
  • read_thread流程
  • read_thread主要函数分析
  • 结论
  • 参考资料
相关产品与服务
云直播
云直播(Cloud Streaming Services,CSS)为您提供极速、稳定、专业的云端直播处理服务,根据业务的不同直播场景需求,云直播提供了标准直播、快直播、云导播台三种服务,分别针对大规模实时观看、超低延时直播、便捷云端导播的场景,配合腾讯云视立方·直播 SDK,为您提供一站式的音视频直播解决方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档