热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

FFmpeg音视频同步原理与实现

一、音视频同步原理如果简单的按照音频的采样率与视频的帧率去播放,由于机器运行速度,解码效率等种种造成时间差异的因素影响,很难同步ÿ

一、音视频同步原理

如果简单的按照音频的采样率与视频的帧率去播放,由于机器运行速度,解码效率等种种造成时间差异的因素影响,很难同步,音视频时间差将会呈现线性增长。所以要做音视频的同步,有三种方式:

参考一个外部时钟,将音频与视频同步至此时间。我首先想到这种方式,但是并不好,由于某些生物学的原理,人对声音的变化比较敏感,但是对视觉变化不太敏感。所以频繁的去调整声音的播放会有些刺耳或者杂音吧影响用户体验。(ps:顺便科普生物学知识,自我感觉好高大上_)。

  • 以视频为基准,音频去同步视频的时间。不采用,理由同上。

  • 以音频为基准,视频去同步音频的时间。 所以这个办法了。

所以,原理就是以音频时间为基准,判断视频快了还是慢了,从而调整视频速度。其实是一个动态的追赶与等待的过程。

二、一些概念

音视频中都有DTS与PTS。

DTS ,Decoding Time Stamp,解码时间戳,告诉解码器packet的解码顺序。
PTS ,Presentation Time Stamp,显示时间戳,指示从packet中解码出来的数据的显示顺序。
音频中二者是相同的,但是视频由于B帧(双向预测)的存在,会造成解码顺序与显示顺序并不相同,也就是视频中DTS与PTS不一定相同。

时间基 :看FFmpeg源码

/**
 * This is the fundamental unit of time (in seconds) in terms
 * of which frame timestamps are represented. For fixed-fps content,
 * timebase should be 1/framerate and timestamp increments should be
 * identically 1.
 * This often, but not always is the inverse of the frame rate or field rate
 * for video.
 * - encoding: MUST be set by user.
 * - decoding: the use of this field for decoding is deprecated.
 *             Use framerate instead.
 */

AVRational time_base;
/**
* rational number numerator/denominator
*/

typedef struct AVRational{
   int num; ///
   int den; ///
} AVRational;

个人理解,其实就是ffmpeg中的用分数表示时间单位,num为分子,den为分母。并且ffmpeg提供了计算方法:

/**
* Convert rational to double.
* @param a rational to convert
* @return (double) a
*/

static inline double av_q2d(AVRational a){
   return a.num / (double) a.den;
}

所以 视频中某帧的显示时间 计算方式为(单位为妙):

time = pts * av_q2d(time_base);

三、同步代码
1、 音频部分
clock 为音频的播放时长(从开始到当前的时间)

if (packet->pts != AV_NOPTS_VALUE) {
    audio->clock = av_q2d(audio->time_base) * packet->pts;
}

然后加上此packet中数据需要播放的时间

double time = datalen/((double) 44100 *2 * 2);
audio->clock = audio->clock +time;

datalen为数据长度。采样率为44100,采样位数为16,通道数为2。所以 数据长度 / 每秒字节数。

ps:此处计算方式不是很完美,有很多问题,回头研究在再补上。

四、 视频部分
先定义几个值:

double  last_play  //上一帧的播放时间
   ,play             //当前帧的播放时间
   , last_delay    // 上一次播放视频的两帧视频间隔时间
   ,delay         //两帧视频间隔时间
   ,audio_clock //音频轨道 实际播放时间
   ,diff   //音频帧与视频帧相差时间
   ,sync_threshold //合理的范围
   ,start_time  //从第一帧开始的绝对时间
   ,pts
   ,actual_delay//真正需要延迟时间
   start_time = av_gettime() / 1000000.0;
//        获取pts
       if ((pts = av_frame_get_best_effort_timestamp(frame)) == AV_NOPTS_VALUE) {
           pts = 0;
       }
       play = pts * av_q2d(vedio->time_base);
//        纠正时间
       play = vedio->synchronize(frame, play);
       delay = play - last_play;
       if (delay <&#61; 0 || delay > 1) {
           delay &#61; last_delay;
       }
       audio_clock &#61; vedio->audio->clock;
       last_delay &#61; delay;
       last_play &#61; play;
//音频与视频的时间差
       diff &#61; vedio->clock - audio_clock;
//        在合理范围外  才会延迟  加快
       sync_threshold &#61; (delay > 0.01 ? 0.01 : delay);
       if (fabs(diff) <10) {
           if (diff <&#61; -sync_threshold) {
               delay &#61; 0;
           } else if (diff >&#61; sync_threshold) {
               delay &#61; 2 * delay;
           }
       }
       start_time &#43;&#61; delay;
       actual_delay &#61; start_time - av_gettime() / 1000000.0;
       if (actual_delay <0.01) {
           actual_delay &#61; 0.01;
       }
//  休眠时间 ffmpeg 建议这样写  为什么 要这样写 有待研究
       av_usleep(actual_delay * 1000000.0 &#43; 6000);
纠正play &#xff08;播放时间&#xff09;的方法 repeat_pict / (2 * fps) 是ffmpeg注释里教的
synchronize(AVFrame *frame, double play) {
   //clock是当前播放的时间位置
   if (play !&#61; 0)
       clock&#61;play;
   else //pst为0 则先把pts设为上一帧时间
       play &#61; clock;
   //可能有pts为0 则主动增加clock
   //需要求出扩展延时&#xff1a;
   double repeat_pict &#61; frame->repeat_pict;
   //使用AvCodecContext的而不是stream的
   double frame_delay &#61; av_q2d(codec->time_base);
   //fps
   double fps &#61; 1 / frame_delay;
   //pts 加上 这个延迟 是显示时间  
   double extra_delay &#61; repeat_pict / (2 * fps);
   double delay &#61; extra_delay &#43; frame_delay;
   clock &#43;&#61; delay;
   return play;
}

原创作者&#xff1a;Learner_9a9e
原文链接&#xff1a;https://www.jianshu.com/p/3578e794f6b5
校验&#xff1a;逆流的鱼yuiop

640?wx_fmt&#61;jpeg


推荐阅读
  • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
    本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 设计模式——模板方法模式的应用和优缺点
    本文介绍了设计模式中的模板方法模式,包括其定义、应用、优点、缺点和使用场景。模板方法模式是一种基于继承的代码复用技术,通过将复杂流程的实现步骤封装在基本方法中,并在抽象父类中定义模板方法的执行次序,子类可以覆盖某些步骤,实现相同的算法框架的不同功能。该模式在软件开发中具有广泛的应用价值。 ... [详细]
  • 本文介绍了Python语言程序设计中文件和数据格式化的操作,包括使用np.savetext保存文本文件,对文本文件和二进制文件进行统一的操作步骤,以及使用Numpy模块进行数据可视化编程的指南。同时还提供了一些关于Python的测试题。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了Codeforces Round #321 (Div. 2)比赛中的问题Kefa and Dishes,通过状压和spfa算法解决了这个问题。给定一个有向图,求在不超过m步的情况下,能获得的最大权值和。点不能重复走。文章详细介绍了问题的题意、解题思路和代码实现。 ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
author-avatar
大好
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有