ffplay实现原理

1. ffmpeg是音视频界的瑞士军刀:

  1. 它提供了从录制、编码、封装、推流到拉流、解封装、解码、滤镜、播放的完整解决方案。
  2. 它是跨平台的解决方案,一套代码适配Windows、Linux、Mac OS、iOS、Andriod。
  3. 它原生支持hls、dash、rtmp、rtsp、http、tcp等主流协议,并提供统一的扩展方式方便扩展自己的协议,比如tutk、webrtc等,目前已经有开源库metaRTC可通过扩展集成到ffmpeg中实现webrtc的推拉流。
  4. 它支持或插件支持所有封装格式的封装和解封装。
  5. 它支持或插件支持所有音视频编码格式的编码和解码。主要视频代表有H264、H265,主要音频代表有aac、opus。
  6. 它已经内置一百多种滤镜并提供方式可以添加自定义滤镜。也可以在这个地方添加OpenGL,支持自定义GPU管线。
  7. 它解码后的音视频数据可以通过SDL(对不同平台提供一套统一的接口,调用不同的底层API库)去渲染视频和播放音频。可以通过OpenGLES去渲染视频,OpenGLES是OpenGL的子集,所以OpenGLES也可以在PC端渲染。

2. ffplay实现原理

1. ffplay是ffmpeg官方提供的播放器

它基于ffmpeg的强大功能,结合SDL实现了从拉流、解协议、解封装、音频解码、视频解码、音频播放、视频播放的一体化功能。

2. 以下是ffplay整体播放流程图:

Created with Raphaël 2.3.0 开始/©王方帅 拉流 解协议 是音频包? 音频解码 音频播放(音视频同步) 结束/©王方帅 是视频包? 视频解码 视频播放(音视频同步) yes no yes

3. ffplay有5个主要线程:

  1. 解协议、解封装线程: read_thread
  2. 音频解码线程:audio_thread
  3. 音频播放线程:sdl_audio_callback
  4. 视频解码线程:video_thread
  5. 视频播放线程(主线程):video_refresh

以上各线程之间各司其职、通力合作、有任务处理任务、无任务休眠等待其他线程唤醒继续执行任务。

4. 以下是各线程执行结构体关系图:

  1. read_thread

    负责解协议、解封装、将AVPacket放入音频PacketQueue或视频PacketQueue:

通过avformat_open_input解协议+解封装
no
yes
yes
no
yes
read_thread/王方帅
filename
AVFormatContext
stream_component_open根据解封装后音视频codec_id初始化Decoder
for循环
stream_has_enough_packets/audioq/videoq
enough
av_read_frame
AVPacket
SDL_CondWait
音频包?
packet_queue_put/audioq
put_finish/王方帅
视频包?
packet_queue_put/videoq

  2. audio_thread

    负责将音频队列里的AVPacket解码成AVFrame,经过滤镜处理后放入音频FrameQueue:

   1. decoder_decode_frame:

no
yes
no
yes
decoder_decode_frame/王方帅
for循环
avcodec_receive_frame接收解码后的帧
接收到帧
dowhile循环
returnFrame/王方帅
PacketQueue.nb_packets为0?
为0
调用packet_queue_get获取pkt
avcodec_send_packet将pkt发送到Decoder中
SDL_CondSignal通知read_thread读取

   2. audio_thread:

yes
no
audio_thread/王方帅
decoder_decode_frame传入音频Decoder拿到音频帧
av_buffersrc_add_frame将帧传入滤镜
av_buffersink_get_frame从滤镜取出结果帧
frame_queue_peek_writable
FrameQueue满了?
满了
SDL_CondWait直到播放线程调用frame_queue_next后Singal继续执行
frame_queue_push到sampq
SDL_CondSignal通知sdl_audio_callback去继续播放/王方帅

  3. sdl_audio_callback

    音频播放回调线程:
   1. synchronize_audio:

yes
no
synchronize_audio/王方帅
wanted_nb_samples赋初值为nb_samples
判断不等于AV_SYNC_AUDIO_MASTER则需要进行同步音频处理
不等于?
同步音频
获取音频与主MASTER时钟的diff
根据diff乘以采样率得到需要增加或减少的样本数
跟原有样本数累加后限制一下最小值和最大值
返回同步计算后需要的样本数wanted_nb_samples/王方帅
返回wanted_nb_samples/王方帅

   2. audio_decode_frame:

yes
no
yes
no
audio_decode_frame/王方帅
dowhile循环
调用frame_queue_peek_readable方法从sampq中取AVFrame
aframe的serial不等于audioq.serial?
不等于
根据音频格式,样本数,通道数计算data_size
调用synchronize_audio计算wanted_nb_samples
aframe跟SDL_OpenAudioDevice得到的音频格式,采样率,声道数,wanted_nb_samples做比较
有任意一个不同?
任意一个不同则初始化音频重采样上下文swr_ctx
调用swr_set_compensation设置重采样参数
调用swr_convert进行重采样
设置audio_buf为重采样后的buf
使用pts显示时间戳更新audio_clock
设置audio_buf为原始buf
使用pts显示时间戳更新音频时钟
返回重采样后的或原始的data_size/王方帅

   3. sdl_audio_callback:

yes
no
sdl_audio_callback/王方帅
获取audio_callback_time
往steam指针指向的内存中存大小为len的音频数据
while循环如果没存满
调用audio_decode_frame方法获取一帧buffer数据填入steam中
steam指针往前偏移,len减去对应buf_size
若一帧buf_size大于len则仅存len大小数据,多余的放到VideoState中以便下次继续填充
根据audio_decode_frame设置的audio_clock
经过计算更新audio_callback_time的clock
结束callback/王方帅

  3. video_thread

    负责将视频队列里的AVPacket解码成AVFrame,经过滤镜处理后放入视频FrameQueue:

yes
no
video_thread/王方帅
for循环
调用decoder_decode_frame从videoq中取AVPacket解码成AVFrame
调用av_buffersrc_add_frame传入AVFrame
调用av_buffersink_get_frame_flags获取滤镜处理后的AVFrame
调用queue_picture
调用frame_queue_peek_writable判断pictq是否已满
已满?
SDL_CondWait等待播放线程取走AVFrame
将封装了AVFrame的Frame对象放入pictq队列
通知视频播放线程取AVFrame进行播放/王方帅

  4. video_refresh

    视频播放线程负责从pictq队列取AVFrame渲染到SDLWindow中:

yes
no
no
yes
yes
no
no
yes
video_refresh/王方帅
retry
调用frame_queue_nb_remaining判断pictq是否为空
为空?
什么也不做/王方帅
判断Frame的serial是否不等于videoq的serial
不等?
调用compute_target_delay方法进行视频时钟同主时钟的同步
如果视频时钟慢,则返回delay为0,以便丢弃帧追赶时钟
如果视频时钟快的超过一个同步阈值,则将正常delay加上时钟的diff返回
如果视频时钟快但在阈值内,则进行2倍delay返回处理
如果delay加帧起始时间大于当前时间
大于?
调用frame_queue_next取下一帧
通知视频解码线程继续解码存pictq
当前帧继续展示/王方帅
调用frame_queue_peek_next取下一帧视频
计算duration加帧起始时间依然大于当前时间
大于?
说明这一帧就是要重新渲染到窗口的帧
设置force_refresh为1
调用video_display方法渲染帧到SDL窗口
渲染时调用video_image_display从pictq取出要渲染的帧,计算帧要显示的x,y,width,height,将帧数据转为SDL_Texture,渲染到SDL_Renderer上/王方帅
调用frame_queue_next将帧从picq中移除,然后SDL_CondSignal视频解码线程继续解码帧

3. 优势分析

  1. ffmpeg实际是一个音视频框架,如果想要自定义一个协议进行播放只要按照规则实现一套对应的协议即可实现ffmpeg对此协议的支持,可以参考metaRTC这个例子。
  2. ffmpeg有统一的编解码代码实现,使得切换编解码器简直不要太容易,只需要将此编解码器编译到ffmpeg库中,然后修改一下编解码器对应的ID即可实现底层编解码器的切换,比如H264升级到H265将大幅降低服务器存储空间及传输带宽的占用,对直播和录播场景将节约不可估量的成本。
  3. ffmpeg配合SDL、OpenGLES将实现全平台的视频播放及特效处理。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王方帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值