超详细解析FFplay之音视频解码线程

1.解码线程
ffplay的解码线程独?于数据读线程 , 并且每种类型的流(AVStream)都有其各?的解码线程 , 如:
(1)video_thread?于解码videostream 。
(2)audio_thread?于解码audiostream 。
(3)subtitle_thread?于解码subtitlestream 。
为?便阅读 , 先列?张表格 , 梳理各个变量、函数名称 。
超详细解析FFplay之音视频解码线程
文章图片
其中PacketQueue?于存放从read_thread取到的各?播放时间内的AVPacket 。 FrameQueue?于存放各?解码后的AVFrame 。 Clock?于同步?视频 。 解码线程负责将PacketQueue数据解码为AVFrame , 并存?FrameQueue 。 对于不同流 , 其解码过程?同?异 。
/***解码器封装*/typedefstructDecoder{AVPacketpkt;//数据包队列PacketQueue*queue;//解码器上下?AVCodecContext*avctx;//包序列intpkt_serial;//=0 , 解码器处于?作状态;=?0 , 解码器处于空闲状态intfinished;//=0 , 解码器处于异常状态 , 需要考虑重置解码器;=1 , 解码器处于正常状态intpacket_pending;//检查到packet队列空时发送signal缓存 , read_thread读取数据SDL_cond*empty_queue_cond;//初始化时是stream的starttimeint64_tstart_pts;//初始化时是stream的time_baseAVRationalstart_pts_tb;//记录最近?次解码后的frame的pts , 当解出来的部分帧没有有效的pts时则使?next_pts进?推算int64_tnext_pts;//next_pts的单位AVRationalnext_pts_tb;//线程句柄SDL_Thread*decoder_tid;}Decoder;解码器相关的函数
decoder我们ffplay?定义 , 重新封装的 。 avcodec才是ffmpeg的提供 。
初始化解码器 。
voiddecoder_init(Decoder*d,AVCodecContext*avctx,PacketQueue*queue,SDL_cond*empty_queue_cond);
启动解码器
intdecoder_start(Decoder*d,int(*fn)(void*),constchar*thread_name,void*arg)
解帧
intdecoder_decode_frame(Decoder*d,AVFrame*frame,AVSubtitle*sub);
终?解码器
voiddecoder_abort(Decoder*d,FrameQueue*fq);
销毁解码器
voiddecoder_destroy(Decoder*d);
使??法如下:
(1)启动解码线程
decoder_init() 。
decoder_start() 。
(2)解码线程具体流程
decoder_decode_frame()
(3)退出解码线程
decoder_abort()
decoder_destroy()
2.视频解码线程
数据来源:从read_thread线程?来 。
数据处理:在video_thread进?解码 , 具体调?get_video_frame 。
数据出?:在video_refresh读取frame进?显示 。
video_thread() , 先看video_thead , 对于滤镜部分(CONFIG_AVFILTER定义部分) , 暂时不做分析 , 需要知道有这个功能就可以 , 简化后的代码如下:
staticintvideo_thread(void*arg){VideoState*is=arg;//分配解码帧AVFrame*frame=av_frame_alloc();//ptsdoublepts;//帧持续时间doubleduration;intret;//1获取streamtimebaseAVRationaltb=is->video_st->time_base;//2获取帧率 , 以便计算每帧picture的durationAVRationalframe_rate=av_guess_frame_rate(is->ic,is->video_st,NULL);if(!frame)returnAVERROR(ENOMEM);for(;;){//循环取出视频解码的帧数据//3解码获取?帧视频画?ret=get_video_frame(is,frame);if(ret<0)gotothe_end;//解码结束,什么时候会结束if(!ret)//没有解码得到画?,什么情况下会得不到解后的帧continue;//4计算帧持续时间和换算pts值为秒//没有帧率时则设置为0,有帧率帧计算出帧间隔duration=(frame_rate.num&&frame_rate.den?av_q2d((AVRational){frame_rate.den,27frame_rate.num}):0);//根据AVStreamtimebase计算出pts值,单位为秒pts=(frame->pts==AV_NOPTS_VALUE)?NAN:frame->pts*av_q2d(tb);//5将解码后的视频帧插?队列ret=queue_picture(is,frame,pts,duration,frame->pkt_pos,is->viddec.pkt_serial);//6释放frame对应的数据//正常情况下frame对应的buf以被av_frame_move_refav_frame_unref(frame);if(ret<0)//返回值?于0则退出线程gotothe_end;}the_end:av_frame_free(&frame);//释放framereturn0;}在该流程中 , 当调?函数返回值?于<0时则退出线程 。 线程的步骤如下:
(1).获取streamtimebase , 以便将frame的pts转成秒为单位
(2).获取帧率 , 以便计算每帧picture的duration
(3).获取解码后的视频帧 , 具体调?get_video_frame()实现
(4).计算帧持续时间和换算pts值为秒
(5).将解码后的视频帧插?队列 , 具体调?queue_picture()实现
(6).释放frame对应的数据
下面重点说说get_video_frame()和queue_picture() 。
get_video_frame简化如下:
staticintget_video_frame(VideoState*is,AVFrame*frame){intgot_picture;//1.获取解码后的视频帧if((got_picture=decoder_decode_frame(&is->viddec,frame,NULL))<0){//返回-1意味着要退出解码线程return-1;}if(got_picture){//2.分析获取到的该帧是否要drop掉.....}returngot_picture;}主要流程:
(1)调?decoder_decode_frame解码并获取解码后的视频帧 。
(2)分析如果获取到帧是否需要drop掉 。 逻辑就是如果刚解出来就落后主时钟 , 那就没有必要放?Frame队列 , 再拿去播放 , 但是也是有?定的条件的 , ?下?分析 。
被简化的部分主要是针对丢帧的?个处理:
if(got_picture){2//2.分析获取到的该帧是否要drop掉,该机制的?的是在放?帧队列前先drop掉过时的视频帧3doubledpts=NAN;45if(frame->pts!=AV_NOPTS_VALUE)6dpts=av_q2d(is->video_st->time_base)*frame->pts;//计算出秒为单位的pts78frame->sample_aspect_ratio=av_guess_sample_aspect_ratio(is->ic,is->video_st,frame);910if(framedrop>0||//允许drop帧11(framedrop&&get_master_sync_type(is)!=AV_SYNC_VIDEO_MASTER))//?视频同步模式12{13if(frame->pts!=AV_NOPTS_VALUE){//pts值有效先确定进?丢帧检测流程 , 控制是否进?丢帧检测有3种情况:
(1)控制是否丢帧的开关变量是framedrop , 为1 , 则始终判断是丢帧 。
(2)framedrop为0 , 则始终不丢帧 。
(3)framedrop为-1(默认值) , 则在主时钟不是video的时候 , 判断是否丢帧 。
如果进?丢帧检测流程 , drop帧需要下列因素都成?:
(1)!isnan(diff):当前pts和主时钟的差值是有效值 。
(2)fabs(diff)<AV_NOSYNC_THRESHOLD:差值在可同步范围内 , 这?设置的是10秒 , 意思是如果差值太?这?就不管了了 , 可能流本身录制的时候就有问题 , 这?不能随便把帧都drop掉 。
(3)diff-is->frame_last_filter_delay<0:和过滤器有关系 , 不设置过滤器时简化为diff<0 。
(4)is->viddec.pkt_serial==is->vidclk.serial:解码器的serial和时钟的serial相同 , 即是?少显示了?帧图像 , 因为只有显示的时候才调?update_video_pts()设置到videoclk的serial 。
(5)is->videoq.nb_packets:?少packetqueue有1个包 。
接下来看下真正解码的过程——decoder_decode_frame , 这个函数也包含了对audio和subtitle的解码 , 其返回值:
-1:请求退出解码器线程
0:解码器已经完全冲刷 , 没有帧可读 , 这?也说明对应码流播放结束 。
1:正常解码获取到帧 。
【超详细解析FFplay之音视频解码线程】先看简化后的主?代码(注意for(;;)这个?循环):


    推荐阅读