ijkplayer系列11:read_thread
Posted 有心好书
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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中。这个方法的代码太长了,这里就不再另外贴出来了。
准备工作
- 调用avformat_alloc_context()方法创建AVFormatContext实例,该实例是流操作的最基础对象。
- 调用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");
- 调用avformat_open_input()开启输入流。对于rtmp协议来说,就是连接rtmp服务器并进行握手,获取audio/video sequence header中携带的元信息。
如果前面一个步骤没有成功获取到AVInputFormat信息,则在这个步骤中会读取若干帧数据来分析并得到格式信息,有性能上的损耗。 - 调用avformat_find_stream_info()获取部分帧并分析流信息。
- 调用stream_component_open()分别开始audio、video对应的原始流,主要是开启对应的解码器,解码线程在这个方法中启动。
- 将状态变更为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流程分析,不错的文章,虽然没有很深入,但至少能够了解到大致的流程,熟悉源码前可以先阅读下。
以上是关于ijkplayer系列11:read_thread的主要内容,如果未能解决你的问题,请参考以下文章
ijkplayer系列12:video_refresh_thread