AV音视频播放同步问题

news/2025/1/17 6:09:17/
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=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 表示视频

           



http://www.ppmy.cn/news/911636.html

相关文章

dshow-001 介绍

1 dshow是基于windows平台的一种流媒体机制 可以从基于wdm的数字/模拟设备中捕获,也可以直接从windows的视频中捕获 自动检测用户的音视频设备,自动硬件加速 2 dshow是基于com的技术,c++设计,没有管理api dshow可以轻松完成媒体回放/格式转换/捕获任务 3 使用dshow时对com…

MIPS CPU 简单分类

分类: - R2000/R3000MIPS I Intruction set - R4000MIPS III Intruction set-- MIPS32 4Kfive-stage pipeline32bit address and data pathscache 16KB 4KE4KC4KF-- MIPS32 24Khttp://www.mips.com/products/processors/32-64-bit-cores/mips32-24k/Release 2 of the MIPS32 ar…

DirectShow笔记

DirectShow是Windows平台的流媒体框架。 CrossBar&#xff1a;a device might have multiple inputs,such as S-Video and composite video,The CrossBar filter enables the application to select the input. CrossBar可以在多路输入之间切换输入。 device pin&#xff1a;…

前端css

1、div中嵌套div时margin-top失效问题 问题现象 div中嵌套div margin-top失效(实际上是作用在了父元素上),div与内部div如果没有设置padding和border那么两个div的边框将重叠视为一条线部分彼此,此时将内部div设置margin-top将作用在父div所以就失效了。 借鉴别人的说法:…

TS解析

传输流Transport Stream(通常称为一路码流)&#xff0c;是最基本的传输实现&#xff0c;数据最终以码流的方式输出。码流部分其实就是DVB协议的最底层&#xff0c;类似于TCP/IP协议的数据链路层&#xff0c;这一层关心的是数据打包&#xff0c;数据帧结构和传输&#xff0c;而不…

网络传输基本概念

交换机与路由器 1、交换机&#xff1a;交换机工作在数据链路层&#xff0c;根据MAC寻址&#xff0c;交换机根据目的MAC地址查询MAC表&#xff0c;转发到目的主机端口。 2、路由器&#xff1a;路由器工作在网络层&#xff0c;根据IP寻址&#xff0c;使用路由表查询下一跳IP地址…

Android Hook技术实战详解

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂&#xff0c;风趣幽默"&#xff0c;感觉非常有意思,忍不住分享一下给大家。 &#x1f449;点击跳转到教程 前言&#xff1a; 什么是Android Hook技术&#xff1f; Android Hook技术是指在Android…

[Android] 安卓迅雷带云盘内测版7.0 简洁 无广告 官方版

迅雷内测版本 界面 简洁 无广告 带云盘功能 我也不多说 大家都知道迅雷APP 版本德性 全是广告 直接上图 跟链接 对于我说 内测版本 属实香 看不到一点广告 下载地址: https://n802.com/file/349707-458153240 http://www.yimuhe.com/file-4770885.html http://www.369pan.c…