当前位置: 首页 > news >正文

FFmpeg音视频同步思路

文章目录

  • 使用音频pts为基准同步音视频的好处
  • 拿到音频PTS
  • 同步PTS的计算
    • 重新计算音频PTS
  • 拿到视频PTS
    • B帧对解码的影响
    • 帧分割/合并,解码延迟/缓冲的影响
  • 进行音视频间的同步处理

使用音频pts为基准同步音视频的好处

  • 符合人类感知特性
    听觉对延迟更敏,这种 “音频为主,视频跟随” 的策略,能在保证音质的前提下,通过最小化视觉干扰实现同步
  • 感知优先级
    优先保证听觉体验,避免 “声画脱节” 带来的违和感。
  • 调整成本
    视频可灵活丢帧 / 重复帧,调整成本远低于音频变速

拿到音频PTS

通过FFMpeg的api接口拿到音频帧
av_get_bytes_per_sample((AVSampleFormat)frame->format)
在这里插入图片描述

从音频帧中可以拿到frame->pts
然后在音频初始化的时候拿到音频的时间基AVRational* time_base

由于SDL对音频的渲染是基于回调函数

SDL_AudioSpec sdl_spec;
//回调的频率和时机由音频设备的参数(采样率、缓冲区大小等)决定,无需手动干预。
sdl_spec.callback = AudioCallback;
if (SDL_OpenAudio(&sdl_spec, nullptr) < 0)
{cerr << SDL_GetError() << endl;return false;
}
//开始播放 调用 SDL_PauseAudio(0) 后,
// SDL 内部​​会自动调用​​通过 SDL_AudioSpec.callback 设置的音频回调函数
SDL_PauseAudio(0);

我们需要在回调函数里将音频从数据包队列拿出并放到SDL给定的Stream缓冲区,SDL每次渲染一段Stream缓冲区里的音频,

//将音频数据复制到SDL提供的缓冲区(即回调函数的stream参数指向的缓冲区)中,
// SDL就会自动播放这些数据
SDL_MixAudio(stream + mixed_size, // 目标音频缓冲区//vector::data()返回一个指向容器首元素的指针buf.data.data() + buf.offset,// 源音频缓冲区size, volume_);//缓冲区长度 , 源音频的音量

即一帧音频frame从音频队列拿出被放到SDL给定的Stream缓冲区以后才算开始渲染,一段缓冲区可能有很多帧音频Frame,而音视频的同步又不可能在SDL的回调函数里完成,所以我们先只选择这段音频包的第一帧的pts
这一段点很关键!!!我们在此时再记录一下渲染该帧音频的那一刻的时间!!我们记为Last_time
Last_time = clock() / (CLOCKS_PER_SEC / 1000)

这样在我们计算当前音频播放到哪里的时候就非常方便了!!即使这次一次处理的一段音频帧,而我们只有开头的一帧的pts

同步PTS的计算

在新开的专门用于做音视频同步的线程里,基于上面拿到的音频帧来进行计算得到视频帧此时对应的pts
通过FFMpeg的API接口

av_rescale_q(audio_pts, *src_time_base, *des_time_base)

  • audio_pts 要转换的值
  • src_time_base 值 a 所使用的时间基(源时间基)
  • des_time_base 转换到的目标时间基
  • 返回 des_time_base

重新计算音频PTS

但是!!!
我们这里传入的audio_pts需要重新计算!!
在这里插入图片描述
在这里插入图片描述
这时就有一个好办法
就是不管这一帧的pts是什么
我们在计算同步pts的时候在拿到当前时间
cur_time = clock() / (CLOCKS_PER_SEC / 1000)
那么距离上次通过回调函数写入SDL的给定stream缓冲区的时间的增量就是

已播放时长增量(ms) =当前时间(cur_time) - 数据包开始时间(Last_time )

然后再将这段时间转换成pts的时间基
increment_pts = ms / (double)1000 / (double)audio_time_base_

最后audio_pts + increment_pts 就是当前音频播放到的位置的pts!!
再把这个pts带入av_rescale_q()的第一个参数
这样就能得到在该音频时间基下的音频帧的pts转换到视频时间基下的pts了
得到的这个pts就是用于同步的syn_pts!!

拿到视频PTS

avcodec_receive_frame();之后拿到解码完成的一帧 Avframe
此时可以拿到在编码阶段时写入的pts
在这里插入图片描述
注意!!
不能使用av_read_frame()解封装出来的 AVPacket 的pts!!!

大多数情况下,对于简单的、没有 B 帧的视频流,解码器通常会直接将对应的输入 AVPacket.pts 复制到输出 AVFrame.pts,此时两者值相同
但是!!!

存在一些复杂情况会导致 AVFrame.pts 与源 AVPacket.pts 不同或不直接对应

B帧对解码的影响

为了保证解码器能顺利工作,编码器 在输出 AVPacket 时,需要​​重新排序​​,按照​​ 解码顺序 而不是 呈现顺序 排列帧

B帧依赖于其后(未来)的帧才能解码。在编码和封装时,帧是按呈现顺序存储的:

pts (显示时间戳)
I P P B B
1 2 3 4 5

然而在 传输和解码 时,为了能解出 B 帧,它们需要被按解码顺序传输:

dts(解码时间戳)
I P B B P
1 2 4 5 3

(一个 P 帧的解码顺序可能在其应该先显示的 B 帧之前)。
AVPacket.dts 就表示解码顺序

所以在有B帧的视频流中,有可能靠后的pts的帧会比靠前的pts的帧先到解码器!

那这个错误的pts的顺序当然不能被用来做音视频同步!

而解码器输出 AVFrame 的顺序是 显示顺序 (pts 顺序) ​​

解码器负责管理依赖关系和缓冲,确保当它输出一帧时,所有依赖的帧都已准备好。因此,输出的帧严格按照其 pts 从低到高的顺序排列

帧分割/合并,解码延迟/缓冲的影响

  • 帧分割与合并:​​
    一些编码格式(如 H.264/AVC, H.265/HEVC)允许 将一帧视频分割成多个 AVPacket(例如多个 NAL Units)。同样,某些音频编码或打包方式也可能导致多个 AVPacket 最终解码成一个 AVFrame。解码器负责将这些相关的数据包组装成一个完整的帧。
    在这个过程中,只有主要或最后一个 AVPacket 的 pts(或者根据规则计算出的一个 pts)会被用来设置输出 AVFrame.pts。直接使用其中一个 AVPacket.pts 可能不准确。

  • 解码延迟/缓冲:​​
    现代编解码器通常存在解码延迟。例如在解码 H.264 流时,发送第一个包含 SPS/PPS 的 AVPacket 并不会立即产生一个可显示的 AVFrame。解码器可能需要接收多个 AVPacket 后才会输出第一帧。它输出的第一帧 AVFrame.pts 对应于它完成解码所需的那个关键 AVPacket 的 pts(并考虑顺序调整)。在队列中的其他 AVPacket 的 pts 暂时未对应任何输出帧

同步发生在呈现阶段而非传输阶段,因此必须使用解码后帧的时间信息​!!!

进行音视频间的同步处理

avcodec_send_packet()拿到AVPacke之前可以进行音视频间的同步
即直接进行视频ptssyn_pts_之间的比较
然后再决定要不要继续解码

while (!is_exit_)
{//同步if (syn_pts_ >= 0 && cur_pts_ > syn_pts_){MSleep(1);continue;}break;
}

如果视频超前于音频那么就一直阻塞等待
如果视频落后于音频则全力渲染追赶音频
所以只要音频不是特别快(几十倍数速率播放),都不会出现视频赶不上音频的情况

相关文章:

  • 安居客西安网页版seo排名优化推广
  • asp 网站信箱模板电商运营推广
  • 网站可以做10000件事情吗软文代写是什么
  • 淘宝客做网站可行么北京网聘咨询有限公司
  • 南京做网站的公司有哪些合肥网站建设
  • 网站建网站建设企业电话seo代码优化包括哪些
  • 计算机网络 网络层:控制平面(二)
  • 从零开始理解百度语音识别API的Python实现
  • Milvus中 Collections 级多租户 和 分区级多租户 的区别
  • C# .NET Framework 中的高效 MQTT 消息传递
  • 解密 C++ 中的左值(lvalue)与右值(rvalue)的核心内容
  • 命名数据网络 | 数据包(Data Packet)
  • docker 命令
  • 2-深度学习挖短线股-1-股票范围选择
  • 均值 ± 标准差的含义与计算方法‘; Likert 5 分制的定义与应用
  • 解锁AI无限潜能!景联文科技数据产品矩阵再升级:多语言题库、海量语料、垂域代码库,全面赋能大模型训练
  • PHP基础2(流程控制,函数)
  • 小程序入门:本地生活案例之首页九宫格布局渲染
  • 快速在手机上部署YOLOv10模型
  • MySQL备份和恢复
  • Linux——系统操作前言:冯诺依曼体系结构、全局理解操作系统
  • CSS 背景属性用于定义HTML元素的背景
  • GEO生成式引擎优化发展迅猛:热点数智化传播是GEO最佳路径
  • 3步精简Android11预装!瑞芯微开发板系统瘦身实战
  • BUUCTF在线评测-练习场-WebCTF习题[RoarCTF 2019]Easy Calc1-flag获取、解析
  • 部署网站需求全满足:Websoft9 多应用托管一站式方案解析