video_thread视频解码线程分析

Posted Loken2020

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了video_thread视频解码线程分析相关的知识,希望对你有一定的参考价值。

之前在 stream_component_open() 里面的 decode_start() 函数开启了 video_thread 线程,如下:

video_thread 线程主要是负责 解码 PacketQueue 队列里面的 AVPacket 的,解码出来 AVFrame,然后丢给入口滤镜,再从出口滤镜把 AVFrame 读出来,再插入 FrameQueue 队列。流程图如下:

video_thread() 函数里面有几个 CONFIG_AVFILTER 的宏判断,这是判断编译的时候是否启用滤镜模块。默认都是启用滤镜模块的。

下面来分析一下 video_thread() 函数的重点逻辑,如下:

video_thread() 函数里面比较重要的局部变量如下:

1,AVFilterGraph *graph,滤镜容器

2,AVFilterContext *filt_in,入口滤镜指针,指向滤镜容器的输入

3,AVFilterContext *filt_out,出口滤镜指针,指向滤镜容器的输出

4,int last_w ,上一次解码出来的 AVFrame 的宽度,初始值为 0

5,int last_h ,上一次解码出来的 AVFrame 的高度,初始值为 0

6,enum AVPixelFormat last_format ,上一次解码出来的 AVFrame 的像素格式,初始值为 -2

7,int last_serial,上一次解码出来的 AVFrame 的序列号,初始值为 -1

8,int last_vfilter_idx,上一次使用的视频滤镜的索引,ffplay 播放器的命令行是可以指定多个视频滤镜,然后按 w 键切换查看效果的

声明初始化完一些局部变量之后,video_thread() 线程就会进入 for 死循环不断处理任务。

get_video_frame() 函数主要是从解码器读取 AVFrame,里面有一个视频同步的逻辑,同步的逻辑稍微复杂,推荐阅读《FFplay视频同步分析


需要注意的是,last_w 一开始是赋值为 0 的,所以必然不等于解码出来的 frame->width,所以一开始肯定是会调进入那个 if 判断,然后调 configure_video_filters() 函数创建滤镜。

总结一下,释放旧滤镜,重新创建新的滤镜有3种情况:

1,后面解码出来的 AVFrame 如果跟上一个 AVFrame 的宽高或者格式不一致。

2,按了 w 键,last_vfilter_idx != is->vfilter_idxffplay 播放器的命令行是可以指定多个视频滤镜,然后按 w 键切换查看效果的,推荐阅读《FFplay视频滤镜分析》。

3,进行了快进快退操作,因为快进快退会导致 is->viddec.pkt_serial 递增。详情请阅读《FFplay序列号分析》。我也不知道为什么序列号变了要重建滤镜。

这3种情况,ffplay 都会处理,只要解码出来的 AVFrame 跟之前的格式不一致,都会重建滤镜,然后更新 last_xxx 变量,这样滤镜处理才不会出错。

由于每次读取出口滤镜的数据,都会用 while 循环把缓存刷完,不会留数据在滤镜容器里面,所以重建滤镜不会导致数据丢失。


video_thread 线程的逻辑比较简单,复杂的地方都封装在它调用的子函数里面,所以本文简单讲解一下,video_thread() 里面调用的各个函数的作用。

1,get_video_frame(),实际上就是对 decoder_decode_frame() 函数进行了封装,加入了视频同步逻辑。返回值如下:

  • 返回 1,获取到 AVFrame 。
  • 返回 0 ,获取不到 AVFrame 。有3种情况会获取不到 AVFrame,一是MP4文件播放完毕,二是解码速度太慢无数据可读,三是视频比音频播放慢了导致丢帧
  • 返回 -1,代表 PacketQueue 队列关闭了(abort_request)。返回 -1 会导致 video_thread() 线程用 goto the_end 跳出 for(;;) 循环,跳出循环之后,video_thread 线程就会自己结束了。返回 -1 通常是因为关闭了 ffplay 播放器。

更详细的分析请阅读《FFplay视频同步分析


2,configure_video_filters(),创建视频滤镜函数,推荐阅读《FFplay视频滤镜分析》。

3,av_buffersrc_add_frame(),往入口滤镜发送 AVFrame

4,av_buffersink_get_frame_flags(),从出口滤镜读取 AVFrame

滤镜相关的函数推荐阅读 FFmpeg实战之路 一章的 《FFmpeg的scale滤镜介绍


5,queue_picture(),此函数可能会阻塞。只是对 frame_queue_peek_writable() 跟 frame_queue_push() 两个函数进行了封装。

在 audio_thread() 音频线程里面是用 frame_queue_peek_writable() 跟 frame_queue_push() 两个函数来插入 FrameQueue 队列的。

在 video_thread() 视频线程里面是用 queue_picture() 函数来插入 FrameQueue 队列的。

音频解码线程 跟 视频解码线程,有很多类似的地方,跟 FrameQueue 队列相关的函数都在 《FrameQueue队列分析》一文中。


推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:

Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

webrtc音视频解析流程分析

webrtc音视频解析流程包括多个线程:

1. rtp网络流接收线程(rtp stream reciever thread)

2. 音视频解码线程(decode thread)

3. 渲染线程(render thread)

 

rtp网络流接收线程(rtp stream reciever thread):

接收网络rtp包,解析rtp包,得到音视频数据包。将解析出的rtp包,加入到RtpStreamReceiver::frame_buffer_中或最终加入VCMReceiver::jitter_buffer_,解码线程从frame_buffer_或jitter_buffer_中取出帧进行解码。

涉及类:RtpStreamReceiver(rtp_stream_receiver.cc), VideoReceiveStream(video_receive_stream.cc)

函数调用流程: 

VideoReceiveStream::DeliverRtp() =>

rtp_stream_receiver_.DeliverRtp (rtp_stream_receiver.cc) =>

RtpStreamReceiver::ReceivePacket(rtp_stream_reciver.cc) =>

rtp_receiver_->IncomingRtpPacket(rtp_receiver_impl.cc) =>

rtp_media_receiver_->ParseRtpPacket(rtp_receiver_video.cc) =>

  1. depacketizer->Parse(rtp_format_h264.cc) 解析出payload_type, 如sps和pps等。
  2. data_callback_->OnReceivedPayloadData(rtp_stream_reciver.cc) =>

     a)  if(h264) InsertSpsPpsIntoTracker

        packet_buffer_->InsertPacket

     b) video_receiver_->IncomingPacket(video_receiver.cc) => 

     _receiver.InsertPacket (receiver.cc) =>

上面a)和b)步骤是或关系,

a)步骤:

packet_buffer_->InsertPacket(packet_buffer.cc) =>

received_frame_callback_->OnReceivedFrame(rtp_stream_receiver.cc) =>

reference_finder->ManageFrame(rtp_frame_reference_finder.cc) =>

RtpFrameReferenceFinder::ManageFrame =>

a) RtpFrameReferenceFinder::ManageFrameGeneric =>

b) RtpFrameReferenceFinder::ManageFrameV8=>RtpFrameReferenceFinder::CompletedFrameV8 =>

c) RtpFrameReferenceFinder::ManageFrameV9=>RtpFrameReferenceFinder::CompletedFrameV9 => 

frame_callback_->OnCompleteFrame =>

complete_frame_callback_->OnCompleteFrame =>

frame_buffer_->InsertFrame

 

b)步骤

 _receiver.InsertPacket(receiver.cc) =>

jitter_buffer_.InsertPacket(jitter_buffer.cc) =>

decodable_frames_->InsertFrame(jitter_buffer.cc)

 

成员说明:

rtp_receiver_: RtpReceiver, 子类RtpReceiverImpl。

rtp_media_receiver_:RTPReceiverStrategy指针,子类是RTPReceiverAudio和RTPReceiverVideo。

data_callback: RtpData, 子类RtpStreamReceiver。

video_receiver_:VideoReceiver。

packet_buffer_: PacketBuffer。

_receiver: VCMReceiver。

received_frame_callback_: 类RtpStreamReceiver,实现video_coding::OnReceivedFrameCallback。

reference_finder_: 类RtpFrameReferenceFinder。

frame_callback_: 类RtpStreamReceiver,实现video_coding::OnCompleteFrameCallback。

complete_frame_callback_:类VideoReceiveStream, 实现video_coding::OnCompleteFrameCallback。

 

视频解码线程(video decode thread):

从RtpStreamReceiver::frame_buffer_中读取每一帧进行解码。

涉及类:VideoReceiveStream(video_receive_stream.cc)

函数调用流程:

VideoReceiveStream::Decode(video_receive_stream.cc) =>

  video_receiver_->Decode(video_receiver.cc) =>

    1. _codecDataBase.GetDecoder(frame, _decodedFrameCallback) =>

      ptr_decoder_->RegisterDecodeCompleteCallback(_decodedFrameCallback)

    2. if (frame_buffer_->NextFrame) video_receiver_.Decode(frame) (从frame_buffer_取帧)

      else video_receiver_.Decode(kMaxDecodeWaitTimeMs) (从jitter_buffer_取帧) =>

        _receiver.FrameForDecoding(取帧) =>

        jitter_buffer_.NextCompleteFrame

        _deocoder->Decode() (_decoder是具体的decoder,如h264)=>      

      decoded_image_callback_->Decoded(generic_decoder.cc) =>

      _receiveCallback->FrameToRender(video_stream_decoder.cc) =>

      incoming_video_stream_->OnFrame(incoming_video_stream.cc) =>

      render_buffers_->AddFrame(incoming_video_stream.cc)

  rtp_stream_receiver_->FrameDecoded(统计)

成员说明:

 video_receiver_:VideoReceiver。

_decodedFrameCallback: 类VCMDecodedFrameCallback。

_receiveCallback: 类VCMReceiveCallback,子类VideoStreamDecoder。

incoming_video_stream_:  rtc::VideoSinkInterface<VideoFrame>*,子类IncomingVideoStream, WebRtcVideoReceiveStream。

 

渲染线程(render thread):

渲染线程从IncomingVideoStream::render_buffers_ 中读取帧,发送出去。

涉及类:IncomingVideoStream

IncomingVideoStream::IncomingVideoStreamProcess(incoming_video_stream.cc) =>

1. render_buffers_->FrameToRender() =>

2. external_callback_->OnFrame (在VideoReceiveStream::Start 中设置为VideoReceiveStream) =>

config_.renderer->OnFrame (video_receive_stream.cc, WebRtcVideoReceiveStream构造函数中设置config_.render 为自己, 最终传递给VideoReceiveStream::config_)=>

sink_->OnFrame

 

sink设置:

PeerConnection::CreateVideoReceiver(peerconnection.cc) =>

VideoRtpReceiver::VideoRtpReceiver(rtpreceiver.cc) => 

channel_->SetSink(broadcaster_) =>

WebRtcVideoChannel2::SetSink(ssrc, sink) =>

receive_streams_[ssrc]->SetSink(sink)(最终设置WebRtcVideoReceiveStream::sink_)

 

成员说明:

render_buffers_:std::list<VideoRenderFrames>

external_callback_: rtc::VideoSinkInterface<VideoFrame>*。

sink_: rtc::VideoSinkInterface<webrtc::VideoFrame>*, 通过WebRtcVideoReceiveStream::SetSink设置。

broadcaster_: rtc::VideoBroadcaster,继承VideoSinkInterface<webrtc::VideoFrame>,broadcast video frames to sinks 。

receive_streams_: std::map<uint32_t, WebRtcVideoReceiveStream*>。

 

以上是关于video_thread视频解码线程分析的主要内容,如果未能解决你的问题,请参考以下文章

线程及视频解码过程6-16

音视频同步

PyQt5学习笔记--摄像头实时视频展示多线程处理视频编解码

Qt音视频开发13-视频解码线程基类的设计

三线程加速无损视频解码

ijkplayer阅读笔记02-创建音视频读取,解码,播放线程