AV音视频播放同步问题

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 表示视频

           


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值