当前位置: 首页 > news >正文

视频流PS打包方式详解

文章目录

  • 视频流PS打包方式详解

  • 1 PS流概述

  • 2 PS封装格式

    • 2.1 PES格式详解

    • 2.2 PS头封装格式

    • 2.3 PS system header封装格式

    • 2.4 PSM封装格式

  • 3 PS流整包发送和分包发送

1 PS流概述

PS流: Program Stream(节目流),简称PS流,将一个或多个分组但有共同的时间基准的基本数据流(PES)合并成一个整体流,由PS包组成,而一个PS包又由若干个PES包组成(到这里,ES经过了两层的封装)。PS包的包头中包含了同步信息与时钟恢复信息。一个PS包最多可包含具有同一时钟基准的16个视频PES包和32个音频PES包。

ES流: Elementary Stream(基本码流),是由编码器输出的原始基础码流,它只含有解码器所必需的、并与原始图象或原始音频相接近的信息。由由压缩器输出的用于传送 单路视音频信号的原始码流。ES只包含一种内容的数据流,如只含视频或只含音频等。

PES流: Packetized Elementary Streams (分组的ES),ES形成的分组称为PES分组,是用来传递ES的一种数据结构。PES流是ES流经过PES打包器处理后形成的数据流,在这个过程中完成了将ES流分组、打包、加入包头信息等操作(对ES流的第一次打包)。PES流的基本单位是PES包。PES包由包头和payload组成。

PSH:Program Stream pack Header ,是PS包的包头,数据一般以PS头开始

PS system header:Partial system header,系统头,一般没啥用,直接跳过

PSM: Program Stream Map,节目流映射( PSM)提供节目流中基本流的描述及其相互关系,PSM可看作stream_id 值为0xBC的PES包

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

2 PS封装格式

前面已经介绍了基本概念,这里这里以H264为例,介绍下PS流基本封装格式

如果是IDR帧PS封装格式如下:

[PSH][PS system header][PSM][PES header][ES]

实例如下,IDR前包含000001BA/000001BB/000001BC/000001E0

如果是非IDR帧,格式如下:
[PSH][PES header][ES][PES header][ES]
实例如下,P帧前包含000001BA/000001E0

 

音视频复合流,也是在视频IDR时发送PSH、PS system header、PSM

 

下面将详细介绍下每种PS头解析方式

2.1 PES格式详解

PES是对原始ES流进行的第一层封装,PES流的基本单位是PES包,由包头和payload组成,PES和ES一样,都是单一原始码流,一般我们是一帧数据放在一个PES包里面;如果帧太大,也可以分包发送,其包头格式如下:

PES包是由固定包头,可选包头和负载三部分组成,其中固定包头固定6个字节;PES包长度字段占位16bit,最大值为65536,故一帧可能会分为多个PES包,如果PES长度为0,则表示整包发送,需要检查下一个PS头确定包长度。

其详细字段解释如下:

Packet start code prefix:包头起始码,固定为0x000001,占位24bit

Stream id:(UI)PES包中的负载流类型,一般视频为0xe00xEF,音频为0xc00xDF,占位8bit

PES packet length:(UI)PES包长度,包括此字节后的可选包头和负载的长度,占位16bit,当其为0时,需要根据下个PS包头起始码来确定PES包长度

Optional PES Header:可选的PES头字段,解析如下:

(1) ’10’字段:占位2bit;

(2) PES scrambling control:加密模式,占2bit;00未加密,01或10或11由用户定义,一般默认为00;

(3) PES priority:有效负载的优先级,占位1bit;值为1比值为0的负载优先级高,一般默认为0x011;

(4) Data alignment indicator:数据定位指示器,占位1bit,如果时分包发送,第一个为0x01,如果单包发送取值为0x01;

(5) Copyright:版权信息,1为有版权,0无版权,占位1bit,默认值为0x00;

(6) Original or copy:原始或备份,1为原始,0为备份,占位1bit;默认值为0x00;

(7) flags占8bit,最高两位为PTS_DTS_flags,PTS和DTS标志位,10表示首部有PTS字段,11表示有PTS和DTS字段,00表示都没有,01被禁止,不会出现此种情况,一般在分包时,第一个包为11,其他为0;ESCR_flag,ESCR标志,占位1bit,1表示首部有ESCR字段,0则无此字段,默认为0;S_rate_flag:ES_rate字段,占位1bit,1表示首部有此字段,0无此字段,默认为0;DSM_trick_mode_flag:占位1bit,1表示有8位的DSM_trick_mode_flag字段,0无此字段,默认为0;Additional_copy_info_flag:占位1bit,1表示首部有此字段,0表示无此字段,默认为0;PES_CRC_flag:占位1bit,1表示PES分组有CRC字段,0无此字段,默认为0;PES_extension_flag:占位1bit,扩展标志位,置1表示有扩展字段,0无此字段,默认为0

(8) PES header data length:(UI)PES首部中可选字段和填充字段的长度,占位8bit,可选字段的内容由上面7个flags来进行控制, 主要是PTS_DTS_flags控制的PTS和DTS字段,填充字节需要注意的是,当对原始流进行 PES 分组,尤其是需要将一帧信息断开分成多个 PES 分组时,

从第二个分组开始不需要 PTS,PES_header_data_length 和它前面的一个字节又都为 0x00,很可能与后面断开的数据组合形成 0x00 00 01 等类似的伪起始码或关键字,令解码端在收到流不完整时产生误判,因此填充字节 stuffing_byte 至少必须加入 1 byte 以确保这种误判不会发生。同时为了入一些私有信息,目前规定 stuffing_byte 至少必须加入 2byte。

一般PES头可选字节为PTS和DTS,其规则及解释如下:

 PTS/DTS字段:显示时间戳/解码时间戳,每一个字段占位40bit,当PTS_DTS_flags == 10存在PTS,11存在PTS和DTS,00都不存在;时间占用33个bit,PTS和DTS的内容是在这40bit中取33位,解析方式方式相同;

PTS和DTS是相对SCR(系统参考)的时间戳,不是绝对时间,而是以系统频率90000为单位的;

start_code:起始码,占位4bit;若PTS_DTS_flags == ‘10’,则说明只有PTS,起始码为0010;若PTS_DTS_flags == ‘11’,则PTS和DTS都存在,PTS的起始码为0011,DTS的起始码为0001;(PTS的起始码后2个bit与flag相同)

PTS[32…30]:占位3bit;

marker_bit:占位1bit;

PTS[29…15]:占位15bit;

marker_bit:占位1bit;

PTS[14…0]:占位15bit;

marker_bit:占位1bit;

PTS = (PTS1 & 0x0e) << 29 + (PTS2 & 0xfffe) << 14 + (PTS3 & 0xfffe ) >> 1

DTS规则与PTS一致,一般PES header data length是10+填充位,其他可选字段大都不存在,解封装的时候,先计算PES长度,再计算PES header data长度,最后计算出ES的长度及起始位置,计算工时如下:

ESlen = PESlen-2-1-pes_header_data_length

PES包头之后,紧跟着就是原始的视频帧数据(ES)或者音频数据

抓拍实例如下图所示,为H264 P帧整包发送PES封装:

根据上文详解,可知000001E0表示视频包,0x57A6=22438表示PES长度;8C=10001100B,最高位为00,之后PES scrambling control=00,PES priority=1,Data alignment indicator=1,Copyright=0,Original or copy=0;flags=0x80=10000000,PTS_DTS_flags=10表示存在PTS,之后的都为0;PES header data length=09,说明ESlen=22438-2-1-9=22426,H264开始位置位4+2+2+1+9=18,从PES起始字节18个字节之后为0000000161为H264nalu头;根据PTS计算公式,PTS= (0x2D&0x0E)<<29 + (((0xFA<<8)+0x89)&0xFFFE)<<14 + (((0x2B<<8)+0xC5)&0xFFFE)>>1

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

2.2 PS头封装格式

PS头是对PES的进一步封装,是将具有共同时间基准的一个或多个PES包组合而成的单一的数据流,PS流总是以0x000001BA开始,以0x000001B9结束,对于一个PS文件,有且只有一个结束码0x000001B9,不过对于网传的PS流,则应该是没有结束码的;具体PS包头字段组成顺序如下:

pack_start_code:起始码,占位32bit,标识PS包的开始,固定为0x000001BA

‘01’字段:占位2bit

SCR字段:占位46bit,其中包含42bit的SCR值和4个bit的marker_bit值;其中SCR值由system_clock_reference_base和system_clock_reference_extension两部分组成,字节顺序依次是:

(1) system_clock_reference_base [32…30]:占位3bit

(2) marker_bit:占位1bit

(3) system_clock_reference_base [29…15]:占位15bit

(4) marker_bit:占位1bit

(5) system_clock_reference_base [14…0]:占位15bit

(6) marker_bit:占位1bit

(7) system_clock_reference_extension:占位9bit

(8) marker_bit:占位1bit

program_mux_rate字段:速率值字段,占位22bit,正整数,表示P-STD接收此字段所在包的PS流的速率;这个值以每秒50字节作为单位;禁止0值;

Marker_bit:标记字段,占位1bit,固定为’1’;

Marker_bit:标记字段,占位1bit,固定为’1’;

Reserved字段:保留字段,占位5bit;

pack_stuffing_length字段:长度字段,占位3bit;规定了此字段之后填充字段的长度;

stuffing_byte:填充字段,固定为0xFF;长度由前一字段确定;

抓拍数据实例:

根据上文详解,起始头:000001BA;77=01110111B,01占两位,(77EA243E9401)以计算出system_clock_reference_base,结下来三个字节(093007)计算program_mux_rate=(09<<14)&0xC000 | (30<<6)&&C0 |(07>>2)&0x3F;FE最后三位表示pack_stuffing_length=FE&0x03=6,六个填充位之后就是000001BB系统头字段

2.3 PS system header封装格式

PS system header为系统头字段,一般封装和解封装默认值即可,没多大意义,解封装时遇到系统头,我们读取系统头的头部长度,即header_length部分,然后根据系统头部的长度,跳过PS系统头,进入下一个部分,其规则如下:

system_header_start_code字段:系统头部起始码,占位32bit,值固定为0x000001BB,标志系统首部的开始

header_length字段:头部长度字段,占位16bit,表示此字段之后的系统首部字节长度

Marker_bit字段:占位1bit,固定值为1

rate_bound字段:整数值,占位22bit,为一个大于或等于PS流所有PS包中的最大program_mux_rate值的整数;可以被解码器用来判断是否可以对整个流进行解码

Marker_bit字段:占位1bit,固定值为1

audio_bound字段:占位6bit;取值范围0到32间整数;大于或等于同时进行解码处理的PS流中的音频流的最大数目;

fixed_flag字段:标志位,占位1bit;置位1表示固定比特率操作,置位0则为可变比特率操;

CSPS_flag字段:CSPS标志位,占位1bit;置位1表示此PS流满足标准的限制

system_audio_lock_flag字段:标志位,占位1bit,表示音频采样率和STD的system_clock_frequency之间有一特定常数比例关系

system_video_lock_flag字段:标志位,占位1bit,表示在系统目标解码器system_clock_frequency和视频帧速率之间存在一特定常数比例关系

Marker_bit字段:占位1bit,固定值为1

video_bound字段:整数,占位5bit,取值范围0到16;大于或等于同时进行解码处理的PS流中的视频流的最大数目

packet_rate_restriction_flag字段:分组速率限制标志字段,占位1bit,若CSPS_flag == 1,则此字段表示哪种限制适用于分组速率;若CSPS_flag == 0,则此字段无意义

reserved_bits字段:保留字段,占位7bit,固定为’1111111’

LOOP:当下一个bit为1时进入

stream_id字段:占位8bit,表示其后的P-STD_buffer_bound_scale和P-STD_buffer_size_bound字段所涉及的流的编码和基本流的号码;若stream_id ==’1011 1000’,则其后的P-STD_buffer_bound_scale和P-STD_buffer_size_bound字段对应PS流中的所有音频流;若stream_id ==’1011 1001’,则其后的P-STD_buffer_bound_scale和P-STD_buffer_size_bound字段对应PS流中的所有视频流;若取其他值,则应大于’1011 1100’,且按照标准对应Stream id(详见附录1);PS流中的每个原始流都应在每个系统首部中通过这种机制精确地规定一次它的P-STD_buffer_bound_scale和P-STD_buffer_size_bound

‘11’字段:占位2bit

P-STD_buffer_bound_scale字段:占位1bit,表示用来解释后面P-STD_buffer_size_bound字段的比例因子;如果之前的stream_id表示音频流,则此值应为0,若之前的stream_id表示视频流,则此值应为1,对于其他stream类型,此值可以0或1

P-STD_buffer_size_bound字段:占位13bit,无符号整数;大于或等于所有PS流分组的P-STD输入缓冲区大小BSn的最大值;若P-STD_buffer_bound_scale == 0,则P-STD_buffer_size_bound以128字节为单位;若P-STD_buffer_bound_scale == 1,则P-STD_buffer_size_bound以1024字节为单位

抓包实例如下:

起始头为000001BB,之后为其header_length=0x0012=18,跳过18个字节为000001BC,既是PSM包

2.4 PSM封装格式

program_stream_map(PSM)节目流映射提供了关于节目流中原始流以及它们之间相互关系的描述,紧跟在系统头部后面的,作为一个 PES 分组出现,一般出现在IDR帧之前,其封装格式如下:

Packet start code prefix字段:包头起始码,固定为0x000001,占位24bit;与后面的字段map_stream_id一起组成分组开始码,标志着分组的开始

map_stream_id字段:类型字段,标志此分组是什么类型,占位8bit;如果此值为0xBC,则说明此PES包为PSM

program_stream_map_length字段:长度字段,占位16bit;表示此字段之后PSM的总长度,最大值为1018(0x3FA)

current_next_indicator字段:标识符,占位1bit;置位1表示当前PSM是可用的,置位0则表示当前PSM不可以,下一个可用

Reserved:保留字段,占位2bit

program_stream_map_version字段:版本字段,占位5bit;表示PSM的版本号,取值范围1到32,随着PSM定义的改变循环累加;若current_next_indicator == 1,表示当前PSM的版本号,若current_next_indicator == 0,表示下一个PSM的版本号

Reserved:保留字段,占位7bit

marker_bit:标记字段,占位1bit,固定为1

program_stream_info_length字段:长度字段,占位16bit;表示此字段后面的descriptor字段的长度

Descriptor字段:program Stream信息描述字段,长度由前个字段确定

elementary_stream_map_length字段:长度字段,占位16bit;表示在这个PSM中所有ES流信息的总长度;包括stream_type, elementary_stream_id, elementary_stream_info_length的长度,即 N*32bit;是不包括具体ES流描述信息descriptor的长度的

stream_type字段:类型字段,占位8bit;表示原始流ES的类型;这个类型只能标志包含在PES包中的ES流类型;值0x05是被禁止的;常见取值类型有MPEG-4 视频流:0x10;H.264 视频流:0x1B;G.711 音频流:0x90;因为PSM只有在关键帧打包的时候,才会存在,所以如果要判断PS打包的流编码类型,就根据这个字段来判断

elementary_stream_id字段:流ID字段,占位8bit;表示此ES流所在PES分组包头中的stream_id字段的值;其中0x(C0DF)指音频,0x(E0EF)为视频

elementary_stream_info_length字段:长度字段,占位16bit;表示此字段之后的,ES流描述信息的长度

Descriptor:描述信息,长度由前个字段确定;表示此类型的ES流的描述信息,这个描述信息的长度是不包含在elementary_stream_map_length字段里面的

CRC_32:CRC字段,占位32bit,CRC校验值

抓包实例如下:

主要字段解析如下:map_stream_id=0xBC;program_stream_map_length=0x005A=90,说明之后的PSM总长度是90;program_stream_info_length=0x0024=36;跳过36个info描述,直接计算elementary_stream_map_length=0x002C=44;stream_type=1B,常用的streamtype如下:

MPEG-4 视频流: 0x10

H.264 视频流: 0x1B

H。265 视频流:0x24

SVAC 视频流: 0x80

G.711 音频流: 0x90

G.722.1 音频流: 0x92

G.723.1 音频流: 0x93

G.729 音频流: 0x99

SVAC音频流: 0x9B

stream_id = E0,与前面的streamtype一起说明,PS符合流中PES的streamid=E0的流是H264;lementary_stream_map_length=0x0028=40,由于elementary_stream_map_length=44,1字节streamtype,一个字节streamid,两个字节的lementary_stream_map_length,其字节和刚好为44,因此此路PS流只包含一路H264视频。

至此,PS封装格式已经讲解完成,对于PS解封装流程先找到PS包的的起始码0x000001BA位串,然后解析出系统头部字段,之后进入PS包的负载,判断是否有PSM,根据PSM确定payload的PES包中所负载的ES流类型及streamid;然后再根据ES流类型和streamid从PES包中解析出具体的ES流,若要从PS流中找出视频帧类型,必须将PS包解析成ES并组成完整的帧,然后在帧数据开始根据NAL头来进行帧的类型的判断;PS封装流程则相反;

3 PS流整包发送和分包发送

由于PES最大长度为65535(长度占2个字节),如果视频帧长度大于PES长度最大值,有两种方式封装,一种是不分包发送,把PES的长度设置为0,表示长度需要解封装时,根据PS头计算长度;另一种时分包发送,及根据PES可表示的长度,分包发送,解封装时需要解析PS后进行组包,形成完成的视频帧,常见的为分包发送方式,其实例如下:

注意分包发送时,分包的第一个分片Data alignment indicator=1,其他分片等于0,当遇到下个PES包Data alignment indicator为1时,说明当前PES包为最后一个分片,可进行视频帧组合。 

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

相关文章:

  • BIM从业者的焦虑和困惑,你遇到了吗?
  • 携职教育:2022年初级会计考试证书领取流程及所需材料
  • iOS App怎么上架到苹果TestFlight?
  • 自动控制原理6.2---常用校正装置及其特性
  • Android——常用定时器
  • 堆排序-Head Sort
  • 【C++】wav文件解析(兼容性强)
  • 免费查题接口搭建
  • 多目标优化算法|用于全局和工程设计优化的多目标原子轨道搜索 (MOAOS)算法(Matlab代码实现)
  • [C++]:for循环for(int num : nums)
  • 3年测试经验,去面试连25K都拿不到了吗?现在测试这么坑?
  • 网课查题公众号 免费授权搜题接口
  • 一篇文章搞懂java中类以及static关键字执行顺序
  • 新手设计师一定要逛这几个网站
  • 多目标优化算法|基于拥挤距离的有效多目标人工蜂鸟算法,用于解决工程设计问题(Matlab代码实现)
  • jquery ajax学习笔记
  • Js基础知识(一) - 变量
  • Mybatis初体验
  • python 装饰器(一)
  • Python_OOP
  • Spring Boot快速入门(一):Hello Spring Boot
  • SpringBoot 实战 (三) | 配置文件详解
  • springMvc学习笔记(2)
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • 从tcpdump抓包看TCP/IP协议
  • 大型网站性能监测、分析与优化常见问题QA
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 如何实现 font-size 的响应式
  • 使用 @font-face
  • 小程序开发中的那些坑
  • 在Docker Swarm上部署Apache Storm:第1部分
  • 在electron中实现跨域请求,无需更改服务器端设置
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • #if #elif #endif
  • #Linux(make工具和makefile文件以及makefile语法)
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • $(selector).each()和$.each()的区别
  • (20050108)又读《平凡的世界》
  • (Matlab)使用竞争神经网络实现数据聚类
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (补)B+树一些思想
  • (二)Linux——Linux常用指令
  • (二)正点原子I.MX6ULL u-boot移植
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (十八)devops持续集成开发——使用docker安装部署jenkins流水线服务
  • (一)Neo4j下载安装以及初次使用
  • (转)C#调用WebService 基础
  • ***linux下安装xampp,XAMPP目录结构(阿里云安装xampp)
  • .NET Core 和 .NET Framework 中的 MEF2
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • .netcore 6.0/7.0项目迁移至.netcore 8.0 注意事项
  • .NET与 java通用的3DES加密解密方法