这两天发现推流播放视频时播放速度特别快,音频比较正常,明显是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=lengthif (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 packagep += (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 表示视频