这两天发现推流播放视频时播放速度特别快,音频比较正常,明显是av sync的问题
从众多平台及实用性上分析,推流就两种媒体格式:ts 及 pes (apes&vpes)
在这里ts可以使用pcr或者pts进行同步,而pes则使用pts进行同步
引用一个网上典型及通俗的说法:
为了更好地理解基于时间戳的音视频同步方案,下面举一个生活中的例子。假设你和你的一个朋友约好了今天18:00在沪上广场见面,然后一起吃饭,再去打游戏。实际上,这个18:00就是你和你朋友保持同步的一个时间点。结果你17:50就到了沪上广场,那么你必须等你的朋友。10分钟过后,你的朋友还没有到,这时他打来电话说有事耽搁了,要晚一点才能到。你没办法因为你已经在旁边的餐厅预订了位置,如果不马上赶过去,预订就会被取消,于是你告诉你的朋友直接到餐厅碰头吧,要他加快点。于是在餐厅将来的某个时间点就成为你和你朋友的又一个同步点。虽然具体时间不定(要看你朋友赶过来的速度),但这样努力的方向是对的,你和你朋友肯定能在餐厅见到面。结果呢?你朋友终于在18:30赶过来了,你们最终“同步”了。吃完饭19:30了,你临时有事要处理一下,于是跟你朋友再约好了20:00在附近的一家游戏厅碰头。你们又不同步了,但在游戏厅将来的某个时间点你们还是会再次同步的。
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; }
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; }
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 表示视频