AV音视频播放同步问题

FFmpeg播放器-音视频同步原理 一、为什么要音视频同步 在播放器的开发中,音频和视频都是在各自独立的线程中解码播放的,理论上如果按照各自的帧率进行播放音视频的话是可以同步的,但实际由于受各种因素的影响,音频和视频会慢慢出现不同步的状况,所以解决音视频的同步非常重要 二、音视频同步的方式 音视频的不同方法一般有三种方式可以选择,如下: 1.参考一个外部时钟,将音频与视频同步至此时间 2.以视频为基准,音频去同步视频... 阅读详情
AV音视频播放同步问题


这两天发现推流播放视频时播放速度特别快,音频比较正常,明显是av sync的问题


媒体内容在播放时,最令人头痛的就是音视频不同步。从技术上来说,解决音视频同步问题的最佳方案就是时间戳:首先选择一个参考时钟(要求参考时钟上的时间是线性递增的);生成数据流时依据参考时钟上的时间给每个数据块都打上时间戳(一般包括开始时间和结束时间);在播放时,读取数据块上的时间戳,同时参考当前参考时钟上的时间来安排播放(如果数据块的开始时间大于当前参考时钟上的时间,则不急于播放该数据块,直到参考时钟达到数据块的开始时间;如果数据块的开始时间小于当前参考时钟上的时间,则“尽快”播放这块数据或者索性将这块数据“丢弃”,以使播放进度追上参考时钟)。


从众多平台及实用性上分析,推流就两种媒体格式:ts 及 pes (apes&vpes)

在这里ts可以使用pcr或者pts进行同步,而pes则使用pts进行同步



这里有两个关键点:首先是生成数据流时打上正确的时间戳,然后是播放时基于时间戳控制数据流的播放,进行等待,丢弃,播放等处理。

引用一个网上典型及通俗的说法:

为了更好地理解基于时间戳的音视频同步方案,下面举一个生活中的例子。假设你和你的一个朋友约好了今天18:00在沪上广场见面,然后一起吃饭,再去打游戏。实际上,这个18:00就是你和你朋友保持同步的一个时间点。结果你17:50就到了沪上广场,那么你必须等你的朋友。10分钟过后,你的朋友还没有到,这时他打来电话说有事耽搁了,要晚一点才能到。你没办法因为你已经在旁边的餐厅预订了位置,如果不马上赶过去,预订就会被取消,于是你告诉你的朋友直接到餐厅碰头吧,要他加快点。于是在餐厅将来的某个时间点就成为你和你朋友的又一个同步点。虽然具体时间不定(要看你朋友赶过来的速度),但这样努力的方向是对的,你和你朋友肯定能在餐厅见到面。结果呢?你朋友终于在18:30赶过来了,你们最终“同步”了。吃完饭19:30了,你临时有事要处理一下,于是跟你朋友再约好了20:00在附近的一家游戏厅碰头。你们又不同步了,但在游戏厅将来的某个时间点你们还是会再次同步的。


其实,同步是一个动态的过程,是一个有人等待、有人追赶的过程。同步只是暂时的,而不同步才是常态。人们总是在同步的水平线上振荡波动,但不会偏离这条基线太远。

推流关键点:
1、解析或分离apes及vpes,这个一般vod服务器会分离处理好,另一个就是从本地读取自行分离
2、推送,以每个pes完整包推送
3、计算正确的pts时间或者直接送入pes数据,由下层自行解析

对于分离这块代码比较多而且较复杂,还有版本问题在此不给出,下面代码给出pts计算及pes包注入的具体逻辑处理代码

下面给出两种获取pts时间的代码:

static unsigned int avdec_get_pts_value(unsigned char*buf, int len) {
	unsigned char *p = buf;
	unsigned int pts = 0;

	/* 标准的pes header音频数据头 */
	if ((p[0] == 0x00) && (p[1] == 0x00) && (p[2] == 0x01) && (p[3] == 0xc0
			|| p[3] == 0xe0)) //magic 0xC0(audio) 0xE0(video)
	{
		unsigned char pts_dts_flag = 0;

		pts_dts_flag = p[7] & 0xc0;

		if (pts_dts_flag == 0x80) {
			if ((p[9] & 0xf0) != 0x20) {
				LOGE("this may be a bad pts !");
				return -1;
			}

			/* 计算pts value 时间 */
			pts = 0;
			pts = (unsigned int) (p[9] & 0x0e) << 29;
			pts += (unsigned int) p[10] << 22;
			pts += (unsigned int) (p[11] & 0xfe) << 14;
			pts += (unsigned int) p[12] << 7;
			pts += (unsigned int) (p[13] >> 1) & 0x7f;

			/* 转换成 ms ,只取低32位 */
			pts = pts / 90;
		} else if (pts_dts_flag == 0xc0) {
			if ((p[9] & 0xf0) != 0x30) {
				LOGE("this may be a bad pts !");
				return -1;
			}

			/* 计算pts value 时间 */
			pts = 0;
			pts = (unsigned int) (p) << 29;
			pts += (unsigned int) p[10] << 22;
			pts += (unsigned int) (p[11] & 0xfe) << 14;
			pts += (unsigned int) p[12] << 7;
			pts += (unsigned int) (p[13] >> 1) & 0x7f;

			/* 转换成 ms ,只取低32位 */
			pts = pts / 90;
		} else if ((pts_dts_flag == 0x00) || (pts_dts_flag == 0x40)) {
			LOGE("error dts value ..");
			return -1;
		}
	}

	return pts;
}

第二种获取pts方法,取的是高32位,误差理论值更小


static unsigned int avdec_get_pts_value2(unsigned char*buf, int len) {
	unsigned char *p = buf;
	unsigned int pts = 0, dts = 0;
	unsigned char *sp = NULL;

	/* 标准的pes header音频数据头 */
	if ((p[0] == 0x00) && (p[1] == 0x00) && (p[2] == 0x01) && (p[3] == 0xc0
			|| p[3] == 0xe0)) //magic 0xC0(audio) 0xE0(video)
	{
		p += 7;
		sp = p;

		//开始计算pts时间,其实际为PTS[32..0]
		{
			unsigned char PTS_DTS_flags = (*sp >> 6) & 0x3;
			unsigned char PES_header_data_length;

			sp++;
			PES_header_data_length = *sp;
			sp++;

			if (PTS_DTS_flags == 0x2)//只存在PTS时间
			{
				//unsigned int pts;
				pts = (*sp >> 1) & 0x7;
				pts = pts << 30;
				sp++;
				pts += (*sp) << 22;
				sp++;
				pts += ((*sp) >> 1) << 15;
				sp++;
				pts += (*sp) << 7;
				sp++;
				pts += (*sp) >> 1;
				sp++;

				//换算成ms毫秒,只取了高32位
				pts = pts / 90;
			} else if (PTS_DTS_flags == 0x3) //存在PTS&DTS时间
			{
				pts = (*sp >> 1) & 0x7;
				pts = pts << 30;
				sp++;
				pts += (*sp) << 22;
				sp++;
				pts += ((*sp) >> 1) << 15;
				sp++;
				pts += (*sp) << 7;
				sp++;
				pts += (*sp) >> 1;
				sp++;

				dts = (*sp >> 1) & 0x7;
				dts = dts << 30;
				sp++;
				dts += (*sp) << 22;
				sp++;
				dts += ((*sp) >> 1) << 15;
				sp++;
				dts += (*sp) << 7;
				sp++;
				dts += (*sp) >> 1;
				sp++;

				//换算成ms毫秒,只取了高32位
				pts = pts / 90;
			} else if (PTS_DTS_flags != 0) //ERROR
			{
				LOGE("error flags = %d\n", PTS_DTS_flags);
				pts = -1;
			}
		}
	}

	return pts;
} 



如何分离pes数据(多个pes包打包在一起进行分开成一个个完整的pes数据):

static int avdec_push_data(void*buf, int len) {
	int ret = 0;
	unsigned int reqLen, total;
	unsigned int pes_length;
	unsigned int pts_value = 0;
	unsigned char *ptr = (unsigned char *) buf; //记录缓冲区首地址
	unsigned char *p = (unsigned char *) buf; //消耗数据缓冲区首地址
	unsigned char *tmp = NULL, *q; //数据注入临时缓冲区首地址
	unsigned int consume_byte = 0;

	while (1) {
		if (p - ptr > len) {
			break;
		}

		/*获取PES-HEADER: 0X000001C0 & 0X000001E0 */
		while (p[0] != 0 || p[1] != 0 || p[2] != 0x01 || //packet_start_code_prefix
				((p[3] & 0xe0) != 0xe0 && (p[3] & 0xe0) != 0xc0))
		//判断是否为视频流(e0) 1110 或音频流(c0) 1101
		{
			printf("error not find PES header ..........\n");
			p++;
			consume_byte++; //丢弃这块数据,避免陷入死循环
			if (p - ptr > len) {
				goto LABEL_EXIT;
			}
		}

		pes_length = (p[4] << 8) | p[5]; //PES_packet_length=length
		if (pes_length == 0)//结构不固定,长度未指定
		{
			q = p + 6;

			//寻找下一个流的开头
			while (q[0] != 0 || q[1] != 0 || q[2] != 0x01 || ((q[3] & 0xe0)
					!= 0xe0 && (q[3] & 0xc0) != 0xc0))///(p[3] & 0xf0) != 0xe0
			{
				q++;

				if (q - ptr > len) {
					goto LABEL_EXIT;
				}
			}

			//求的pes的长度
			pes_length = q - (p + 6);
		}

		if (((p + pes_length + 6) - ptr) > len) {
			printf("not enough pes data and wait next package\n");
			break;
		}

		pts_value = avdec_get_pts_value(p, 14);
		//pts_value = avdec_get_pts_value2(p,14);
		if (pts_value < 0) {
			printf("error pts value\n");
			pts_value = -1;
		}

		//push each pes data...

		//move next pes package
		p += (pes_length + 6);
		consume_byte += (pes_length + 6);
	}

	LABEL_EXIT: return consume_byte;
}



开始时考虑的条件不全,经过再三对比规范及分析数据流才将所有的条件才考虑ok,有些细节还是需要注意的。


       将13818-1对于PES句的规范列下来:


     对于pes length 这个字段开始弄错了,以为其是payload data的长度,其实不然。指的是从pes length指的是从计算这个字段长度的后面的长度,其paylaod data len + 8 = pes length 


描述如此写的:
  PES_package_length : A 16 bit fiedl specifying the number of bytes in the PES packet following the
  last byte of the field , A value of 0 inidcated that the PES packet length is neither specified nor 
  bounded and is allowed only in PES packets whose payload is a video elementary stream contained
  in Transport Stream packets

  Stream id 值 :  
0xC0表示音频, 0xE0 表示视频

           


avformat_seek_file 详解:FFmpeg 里那个你一直用错的“真Seek” avformat_seek_file 是 FFmpeg 库中用于在媒体文件里跳转定位的核心函数‌,主要用在播放器进度条拖动、快进快退等场景,能让程序快速找到文件中的指定时间点 。‌‌‌很多人以为 FFmpeg seek 难,是因为他们拿 av_seek_frame当 API 用。avformat_seek_file才是 FFmpeg 留给“想做播放器的人”的那扇正门。 阅读详情

相关推荐

zoujingli-ThinkAdmin-v6.zip

php含源码项目 下载之后本地安装访问即可

深入理解Android音视频同步机制(一)ExoPlayer的avsync逻辑

深入理解Android音视频同步机制(一)ExoPlayer的avsync逻辑

m0_60259116的博客 4117

av-autosync:自动同步音频和视频文件。 基于echoprint

自动同步 威廉·R·兹维克(William R.Zwicky) 自动同步音频和视频文件。 基于echoprint。 scanall.py-扫描给定目录或当前目录,为每个媒体文件做回显,并保存到数据库。

基于AVpro视频播放器的展馆视频播放

using System.Collections; using System.Collections.Generic; using UnityEngine; using RenderHeads.Media.AVProVideo; 脚本不需要赋值,挂在物体上即可 public class VideoShowOne : MonoBehaviour { public static VideoShowOne showOne;bool IsPlay = false; private void Awa

kvkZzzz的博客 24万+

倒计时跳转页面

完成某个操作后,页面会出现倒计时多长时间跳转到另一个页面

从零搭建ROS1导航小车(一):松灵SCOUT Mini底盘通讯配置与ROS包编译避坑指南

本文详细介绍了从零开始搭建ROS1导航小车的初始步骤,重点讲解松灵SCOUT Mini底盘的通讯配置与ROS包编译避坑指南。内容包括硬件连接、CAN通讯配置、ROS工作空间构建及编译优化等实用技巧,帮助开发者快速实现自主导航小车的通讯功能。

weixin_27014595的博客 188

AV视频输出接口类型

转载时请注明出处和作者联系方式:http://blog.youkuaiyun.com/mimepp作者联系方式:YU TAO 在这里列一下视频输出的接口类型, 算是一个笔记. 类型 名称 中文名 接头 含义 特点

mimepp的专栏,这里都是原创 22万+

NVIDIA DGX Spark实战指南:从开箱到AI模型高效部署

本文详细介绍了NVIDIA DGX Spark的开箱体验、硬件架构及AI模型高效部署实战指南。从1 PetaFLOP算力的迷你设备到Grace Blackwell超级芯片的深度解析,再到五分钟快速上手指南和大模型部署技巧,帮助开发者充分利用这款全球最小AI超级计算机的性能优势。

weixin_29239279的博客 1363

一.AV Foundation 视频播放 - 功能综述

通过这篇博客,我们初步认识了iOS中播放器的核心类,以及通过一个简单的播放实例感受了其基本功能。在接下来的系列文章中,我们将深入挖掘播放功能的方方面面。我们将探讨更多高级功能,解析优化策略,以及介绍一些常见的问题和解决方案。如果你有任何问题或建议,欢迎在评论区留言,我们期待在未来的文章中为你解答疑惑。敬请期待下一篇深度剖析!

weixin_39339407的博客 9万+

跟着团子学SAP CO:揭秘结果分析码在项目结算中的实战应用(一)KKA2/CJ88

本文深入解析了SAP CO模块中结果分析码在项目结算中的核心作用。以KKA2(方法10)和事务代码CJ88为例,详细阐述了如何在项目完工后一次性确认收入的场景下,通过结果分析功能实现收入与成本的精准匹配,将项目成本暂估为在制品,确保各期利润的准确反映。

gamma的博客 684

ROBOGUIDE教程:FANUC机器人X型焊枪气动点焊焊接

FANUC机器人X型焊枪气动点焊焊接虚拟仿真实现方法。

IndRobSim 4384

AVPlayer 播放在线视频和本地文件的设置区别

依赖框架都是AVFoundation 需要da

Mr.xiaoma.wen 343万+

PADS安装教程(超详细)

声明:个人实践 一、工具准备 pads9.5完整破解版百度网盘提取码【wjx9】 链接:https://pan.baidu.com/s/1pIP6BJO435ssmF7Hjwyz8A 提取码:wjx9 安装环境:win10 64位 二、开始解压安装 关闭杀毒,exe以管理员身份运行 点击【skip】,不然又得licese 点击【agree】 点击【modify】 设置安装的工具、路径及工程路径 设置完成后,开始【install】 在之后注册。 将Crack复制到PADS安装目录

qq_42057393的博客 5万+

为什么同一首歌在两台设备上老“抢拍”?——我用AVSession / AVPlayer / AVSink搭了个跨设备音视频同步播放,你来不来试戏?

本文介绍了鸿蒙系统下跨设备音视频同步播放的实现方案,主要包括分布式AV流和协同播放两种模式。文章重点讲解了分布式AV流的技术实现,包括基于UDP的自定义传输协议设计、源端编码帧发送逻辑,以及接收端的AVSink模块实现(包含抖动缓冲和时钟同步处理)。同时提供了ArkTS伪代码示例,展示了如何利用鸿蒙的AVPlayer、AVSession等系统能力构建多设备同步播放系统,并讨论了网络时钟同步、播放控制等关键技术要点。文章兼顾实用性和工程深度,适合鸿蒙开发者参考。

2501_92698736的博客 1339

ADRC自抗扰算法优化(PLC梯形图篇)

ADRC自抗扰算法PLC梯形图完整源代码请参看下面博客文章:ADRC自抗扰控制算法(含梯形图完整源代码和算法公式)_adrc算法_RXXW_Dor的博客-优快云博客PLC的自抗扰控制(ADRC)算法_RXXW_Dor的博客-优快云博客_adrc算法1、自抗扰控制算法,网上很多文章有所讲解,大家也可以关注韩京清教授的论文和书籍,这里就不赘述了,下面给出梯形图+SCL代码。

RXXW_Dor的博客 2281

YOLOv8 改进有效涨点专栏目录 | 含卷积、主干、注意力机制、Neck、检测头、损失函数、二次创新C2PSA/C3k2等各种网络结构改进

YOLOv8在目标检测领域的地位与优势模型改进的意义与价值本专栏的特色与创新点读者学习路径建议关键改进点回顾不同场景的模型选型建议开源社区资源汇总持续学习路径指南YOLOv8在目标检测领域的地位与优势模型改进的意义与价值本专栏的特色与创新点读者学习路径建议关键改进点回顾不同场景的模型选型建议开源社区资源汇总持续学习路径指南。

weixin_39818775的博客 1072

播放器中的seek操作实现

av_seek_frame()是FFmpeg中用于媒体文件定位的关键函数,其参数包括格式上下文、流索引、时间戳和定位标志。函数支持多种定位方式:按时间、帧索引或字节位置,并可通过标志组合控制定位行为。实际应用中需要先清空缓存数据,计算目标位置后执行定位。定位流程包括:暂停播放、清空队列、执行seek操作,然后循环读取和解码视频包,直到找到目标位置附近的关键帧进行渲染。最后恢复播放状态并更新界面显示。该函数在实现媒体播放器的进度跳转功能时至关重要。

m0_62092496的博客 560

高温环境下(100℃以上)相机的几种保障方案

高温环境下相机保障措施摘要:针对120℃以上高温环境,相机需采取多重防护:1.散热设计采用金属机身、散热鳍片或风扇;2.关键部件选用耐高温材料(铝合金、特种玻璃);3.电子元件需军规级芯片并做三防处理;4.建议外接电源或耐高温电池。工业相机(如FLIR A655sc、Basler ace)及特种机型(Ximea xiQ)具备高温适应性,普通相机可加装隔热套件配合冷却系统。选择时需关注传感器信噪比和全局快门性能,并设置温度监控保护机制。注意高温会加速设备老化,需定期检测维护。(149字)

start_up_go的博客 1583

IINA播放器性能优化终极指南:告别卡顿,享受流畅观影体验

你是否在观看4K高清视频时遇到过画面卡顿、声音不同步的问题?或者播放网络流媒体时频繁缓冲等待?IINA作为macOS平台上最优秀的开源媒体播放器,内置了强大的性能监控和优化功能。本文将为你提供完整的IINA播放器性能优化解决方案,让你彻底告别播放卡顿,享受流畅的观影体验。 [![IINA播放器默认专辑封面](https://raw.gitcode.com/gh_mirrors/iin/iina/

gitblog_00238的博客 414

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

二次开发开源ffplay播放器下的seek关键

我的优化方案是seek之后先强制渲染几帧视频,让音频线程有时间完成有效音频帧输出并更新主时钟,只有这样音频时钟动起来,视频才能进行同步,不然视频会卡在那里,主要作用就是防止视频依旧对齐未更新的音频时钟导致一直等待。seek后标志位置为false,当音频输出第一帧更新主时钟时,再置为true,视频渲染线程根据这个标志位为true时再进行音视频同步。视频渲染快,音频播放慢,视频线程会等待音频主时钟,等到音频主时钟推进到pts后,视频才渲染,实现音视频同步。6.初始化音频主时钟,为后续音视频同步做准备。

2503_92745132的博客 488

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

ffmpeg开发中的问题(八)

今天发现了几个很重要的问题,现在搞明白了,真是豁然开朗 1. 帧率表示每秒播放多少张图片。一个视频转码后,播放的总时间应该是不变的,帧率变化只会导致总帧数变化。 所以,当高速帧率时,只会观看感觉上有变化,在速度上应该是不变的 2. 之前的同步问题,其实也算不上同步,主要原因是视频速度被人为改变了,音频是正常速度,这样看起来不同步了 解决方法是,改变视频的PTS,这样就可以正常播放了

你越努力,你的运气就会越好 1490

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

音视频同步即时通讯SDK

视频流中的DTS/PTS到底是什么?      DTS(解码时间戳)和PTS(显示时间戳)分别是解码器进行解码和显示帧时相对于SCR(系统参考)的时间戳。SCR可以理解为解码器应该开始从磁盘读取数据时的时间。     mpeg文件中的每一个包都有一个SCR时间戳并且这个时间戳就是读取这个数据包时的系统时间。通常情况下,解码器会在它开始读取mpeg流时启动系统时钟(系统时钟的初始值是第一个数据包

alice185的专栏 606

FFmpeg小技巧:H264转MP4时的时间戳问题解决方案

本文深入探讨了使用FFmpeg将H264裸流封装为MP4文件时常见的时间戳问题。针对PTS/DTS缺失或错乱导致的播放卡顿、音画不同步等现象,提供了从命令行诊断修复到代码级手动计算与转换时间戳的完整解决方案,帮助开发者彻底驯服时间戳这头“野兽”,确保视频流处理的稳定与兼容性。

honey的博客 1008

Audio and Video Synchronization (Amazon Fire TV)

亚马逊的av同步记录一下 https://developer.amazon.com/ja/docs/fire-tv/audio-video-synchronization.html

mandagod的博客 483

音视频开发---音视频同步算法

目录 ffplay简介 为什么要做音视频同步 音视频同步算法 参考 本文是对音视频同步算法的总结,以阅读ffplay.c源码为基础,结合各位博主的分析,逐渐深入理解同步算法原理, 并根据自身理解, 编写一套简易的视频播放器,用于验证音视频同步算法。 ffplay简介 ffplay是FFmpeg提供的开源播放器,基于FFmpeg和SDL进行视频播放, 是研究...

挥剑踏苍穹的博客 6712

基于 FFmpeg 和 SDL 的音视频同步播放器

基于 FFmpeg 和 SDL 的音视频同步播放器

ProgramNovice的博客 2153
上一篇: 荒野生存 观后感
下一篇: Android4.0 与 android2.3 差异
andyhuabing
博客等级 码龄19年 704粉丝 165原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值