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线程中进行时钟同步,同步完后,则会开始绘制第一帧。此时视频开始播放。

参考资料

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏岑玉海

hbase源码系列(六)HMaster启动过程

  这一章是server端开始的第一章,有兴趣的朋友先去看一下hbase的架构图,我专门从网上弄下来的。   按照HMaster的run方法的注释,我们可以了解...

61590
来自专栏坚毅的PHP

[node.js]开放平台接口调用测试

遇到的问题:Node.js JSON parsing error,syntax error unexpect end of input 测试代码 //测试/st...

45360
来自专栏曾大稳的博客

ffmpeg 封装格式转换 MP4转AVI

格式转换直接将视音频压缩码流从一种封装格式文件中获取出来然后打包成另外一种封装格式的文件。因为不需要进行视音频的编码和解码,所以不会有视音频的压缩损伤。

86430
来自专栏分布式系统进阶

Influxdb 数据写入流程

因此对写入请求的处理就在函数 func (h *Handler) serveWrite(w http.ResponseWriter, r *http.Reque...

21330
来自专栏MasiMaro 的技术博文

派遣函数

驱动程序的主要功能是用来处理IO请求,而大部分的IO请求是在派遣函数中完成的,用户模式下所有的IO请求都会被IO管理器封装为一个IRP结构,类似于Windows...

15410
来自专栏Flutter入门

Flutter入门三部曲(3) - 数据传递/状态管理

Flutter数据传递 分为两种方式。一种是沿着数的方向从上向下传递状态。另一种是 从下往上传递状态值。

2.1K40
来自专栏信安之路

HCTF2017的三个WriteUp

解决方法就是先 undefine 掉函数,再右键选择 Code,最后 Create function 就可以正常反编译了。

12600
来自专栏学习力

《Java从入门到放弃》框架入门篇:hibernate中的多表对应关系

23970
来自专栏逆向技术

16位汇编第三讲 分段存储管理思想

      内存分段 一丶分段(汇编指令分段) 1.为什么分段?   因为分段是为了更好的管理数据和代码,就好比C语言为什么会有内存4区一样,否则汇编代码都写...

22760
来自专栏Flutter入门

Flutter入门三部曲(3) - 数据传递/状态管理

这个既熟悉又陌生类可以帮助我们在Flutter中沿着树向下传递信息。这个类只是简单的保存了一个状态而已。

22700

扫码关注云+社区

领取腾讯云代金券