ijkplayer系列11:read_thread

ijkplayer的核心代码在ff_ffplayer.c中,以视频为例(音频类似),其中又以三个线程和两个队列最为核心。 三个线程分别为:
• read_thread:读取流。
• video_refresh_thread:图像渲染。
• ffp_video_thread:图像解码,需要一提的是,硬解时的流程不太一样。
两个队列定义在VideoState结构体中,分别为:
• videoq:存储从流中读取到的未解码的帧
• pictq:存储解码后的原始帧
三个线程均在开启流(stream_open)时启动,read_thread不断从流中读帧并写入videoq中,ffp_video_thread循环从videoq中获取编码帧,解码后将原始帧写入pictq中,video_refresh_thread循环从pictq中获取原始帧进行渲染。这里使用到了生产者-消费者模式,三个线程相互独立,没有任何逻辑上的耦合。

read_thread

read_thread的主要工作是连接服务器并读取帧数据,然后将帧数据写入PacketQueue中。这个方法的代码太长了,这里就不再另外贴出来了。

准备工作

  1. 调用avformat_alloc_context()方法创建AVFormatContext实例,该实例是流操作的最基础对象。
  2. 调用av_find_input_format()获取AVInputFormat信息,该信息在开启流时需要用到。在ijkplayer的原始逻辑里,由于没有得到format name,所以这个步骤不会被调用,我做了以下优化:
 //这里没有指定format name,因此avformat_open_input()时ffmpeg会去检测文件格式,有额外的时间消耗。可以判断如果是rtmp,直接等于"flv"。
    if(av_stristart(is->filename, "rtmp", NULL)) {
        ffp->iformat_name = "flv";
    }
    if (ffp->iformat_name) {
        is->iformat = av_find_input_format(ffp->iformat_name);
        av_log(ic, AV_LOG_INFO, "av_find_input_format:%s %s", ffp->iformat_name, is->iformat ? "ok" : "fail");
    } else {
        av_log(ic, AV_LOG_INFO, "not appoint format name,ffmpeg will prode format");
    }
  1. 调用avformat_open_input()开启输入流。对于rtmp协议来说,就是连接rtmp服务器并进行握手,获取audio/video sequence header中携带的元信息。
    如果前面一个步骤没有成功获取到AVInputFormat信息,则在这个步骤中会读取若干帧数据来分析并得到格式信息,有性能上的损耗。
  2. 调用avformat_find_stream_info()获取部分帧并分析流信息。
  3. 调用stream_component_open()分别开始audio、video对应的原始流,主要是开启对应的解码器,解码线程在这个方法中启动。
  4. 将状态变更为PREPARED并通知外部。

循环读取帧

准备工作完成后,就进入了循环读取和操作流的过程。循环内部主要工作如下:
1、读取帧前判断jitter buffer(PacketQueue)是否已经满了。原始策略是当buffer满时暂停读取直到buffer有空闲空间,当网络较差或抖动比较严重时,这里会造成播放时延不断增大,体验不好。可以在这边修改策略,当buffer满时,清理掉部分帧。

/* if the queue are full, no need to read more */
        if (ffp->infinite_buffer<1 && !is->seek_req &&
#ifdef FFP_MERGE
              (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE
#else
              (is->audioq.size + is->videoq.size + is->subtitleq.size > ffp->dcc.max_buffer_size
#endif
            || (   stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq, MIN_FRAMES)
                && stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq, MIN_FRAMES)
                && stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq, MIN_FRAMES)))) {
            if (!is->eof) {
                ffp_toggle_buffering(ffp, 0);
            }
            /* wait 10 ms */
            SDL_LockMutex(wait_mutex);
            SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
            SDL_UnlockMutex(wait_mutex);
            continue;
        }

2、调用av_read_frame()读取一帧视频或若干帧音频或其他类型的数据包。注意区别于av_read_packet(),后者读取的是一个包,可能是一帧也可能是半帧,不过这个方法在新的版本已经被废弃了,如果有使用过ffmpeg老版本的同学记得区分下就是了。
3、将读取到的并且满足时间条件的帧加入jitter buffer(PacketQueue)中。

/* check if packet is in play range specified by user, then queue, otherwise discard */
        stream_start_time = ic->streams[pkt->stream_index]->start_time;
        pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;
        pkt_in_play_range = ffp->duration == AV_NOPTS_VALUE ||
                (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *
                av_q2d(ic->streams[pkt->stream_index]->time_base) -
                (double)(ffp->start_time != AV_NOPTS_VALUE ? ffp->start_time : 0) / 1000000
                <= ((double)ffp->duration / 1000000);
        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);
        } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
            packet_queue_put(&is->subtitleq, pkt);
        } else {
            av_packet_unref(pkt);
        }

4、调用ffp_statistic_l()统计audio和video的buffer信息。

参考资料

ijkplayer流程分析,不错的文章,虽然没有很深入,但至少能够了解到大致的流程,熟悉源码前可以先阅读下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值