音视频开发系列
文章目录
前言
在安防行业中,gb28181是国内安防行业的标准,onvif是国外的安防行业的标准,其中gb28181中视频流是ps流、由设备、下级平台推送到上级平台,具有从内网向外网推送视频流的能力。
基于 RTP的PS封装首先按照ISO/IEC13818-1:2000将视音频流封装成 PS包,再将 PS包以负载的方式封装成 RTP包。进行PS 封 装 时,应 将 每 个 视 频 帧 封 装 为 一 个 PS 包, 每个关键帧的PS包中应包含系统头和PSM,系统头和PSM放置于PS包头之后、第一个 PES包之前。
一、es、pes、ps、ts流是什么?
ES
ES流(Elementary Stream): 也叫基本码流,包含视频、音频或数据的连续码流.是直接从编码器出来的数据流,可以是编码过的视频数据流(H.264,MJPEG等),音频数据流(AAC),或其他编码数据流的统称。ES流经过PES打包器之后,被转换成PES包。每个ES都由若干个存取单元(AU)组成,每个视频AU或音频AU都是由头部和编码数据两部分组成,1个AU相当于编码的1幅视频图像或1个音频帧,也可以说,每个AU实际上是编码数据流的显示单元,即相当于解码的1幅视频图像或1个音频帧的取样。
PES
PES流(Packet Elementary Stream): 也叫打包的基本码流, 是将基本的码流ES流根据需要分成长度不等的数据包,并加上包头就形成了打包的基本码流PES流.ES形成的分组称为PES分组,是用来传递ES的一种数据结构。PES流的基本单位是PES包。PES包由包头和payload组成。
PS
PS–Program Stream(节目流)PS流由PS包组成,而一个PS包又由若干个PES包组成(到这里,ES经过了两层的封装)。PS包的包头中包含了同步信息与时钟恢复信息。一个PS包最多可包含具有同一时钟基准的16个视频PES包和32个音频PES包。
TS
TS流(Transport Stream): 也叫传输流, 是由固定长度为188字节的包组成,含有独立时基的一个或多个program, 一个program又可以包含多个视频、音频、和文字信息的ES流; 每个ES流会有不同的PID标示. 而又为了可以分析这些ES流, TS有一些固定的PID用来间隔发送program和ES流信息的表格: PAT和PMT表.
ES流通常指的裸流,比如音频视频h264,pcm等。
MPEG-TS则主要应用于实时传送的节目,比如实时广播的电视节目。
MPEG2-PS主要应用于存储的具有固定时长的节目,如DVD电影。
将DVD上的VOB文件的前面截剪掉(或者数据损坏),那么将导致整个文件无法解码,而电视节目是你任何时候打开电视机都能解码(收看)的,所以,MPEG2-TS格式的特点就是要求从视频流的任一片段开始都是可以独立解码的。
二、PS流数据封装
DVD节目中的MPEG2格式,是MPEG2-PS,全称是Program Stream,简称PS流。
国标 GB28181 关于数据封装说明,能够接入 GB28181 平台的 IPC 符合上述标准。参考gb28181文档中,附录C,内容如下:
通过wireshark抓包分析来看,RTP 包由如下结构组成:
首条数据结构:
RTP Header + RTP Payload(PS Header + PS System Header + PSM + PES(Header + Payload))
非首条数据结构:
RTP Header + RTP Payload(PS Header + PES(Header + Payload))
其中RTP Header格式如下:具体内容参考:https://blog.csdn.net/weixin_44834554/article/details/127232152 中rtp章节
以下即为RTP Payload内容
PS数据封装:
1.IDR关键帧封装:
每个IDR NALU 前一般都会包含SPS、PPS 等NALU,因此将SPS、PPS、IDR 的NALU 封装为一个PS 包,包括ps 头,然后加上PS system header,PS system map,PES header+h264 raw data。
所以一个IDR NALU PS 包由外到内顺序是:
PSheader| PS system header | PS system Map | PES header | h264 raw data。
2.非关键帧封装:
PS包从外带内的顺序是:
PSheader| PES header | h264 raw data。
3.含有音频的PS封装:
PS头|PES(video)|PES(audio)
PSheader| PES header | h264 raw data|PES header|AAC raw data
a)一个 PS 流或者文件可以视为多个 PS GOP,每个 GOP 是以 I 帧起始的多帧集合,各 GOP 之间的信息没有相互依赖性,可以进行剪切拼接。
b)一个 PS GOP 由一个或多个 PS 包组成,一个 PS 包内包含一个 PSH(PS Header)和若干个 PES 包。
c)每个 PS GOP 的第一个 PS 包应当在包头 PSH 后立即跟随一个 PSM 包。PSM 包是一种特殊的 PES 包,含有对其他 PES 负载数据类型的描述。
d)PS 包内的其他 PES 的出现顺序和内容没有特殊约束,即一个 PS 包内可以包含交错出现的视频、音频和私有流等 PES 包,各 PES 包根据 PSM 的描述进行拆分。
PS流组成元素的起始标志:
PS Header: 起始码,占位32bit,标识PS包的开始,固定为0x000001BA
SYS Header: 起始码,占位32bit,标识SYS包的开始,固定为0x000001BB
PSM Header: 起始码,占位32bit,标识PSM包的开始,固定为0x000001BC
PES Header: 起始码,占位24bit+8bit,标识PSM包的开始,固定为0x000001,流ID,0x(C0-DF指音频,0x(E0-EF)为视频
PS Header
PS头最小长度14个字节,用来标志PS流的一个包的开始。
SYS Header
目前的系统头部好像是没有用到的,所以对于系统头部的解析,我们一般只要先首先判断是否存在系统头(0x000001BB),然后我们读取系统头的头部长度,即header_length部分,然后根据系统头部的长度,跳过PS系统头,进入下一个部分,即PS的payload,PES包;在固定包头和系统头之后,就是PS包的payload,即PES包;
PSM Header
若PSM存在,则第一个PES包即为PSM。
PSM只有关键帧的时候,才会存在。PSM用来提供es流的描述信息,以及各es流之间的关系。
三、通过wireshark分析PS流数据封装协议
3.1 PS协议
PS Header
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两部分组成;字节顺序依次是:
system_clock_reference_base [32..30]:占位3bit;
marker_bit:占位1bit;
system_clock_reference_base [29..15]:占位15bit;
marker_bit:占位1bit;
system_clock_reference_base [14..0]:占位15bit;
marker_bit:占位1bit;
system_clock_reference_extension:占位9bit;
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;长度由前一字段确定;
SYS Header
系统头部字段,当起始码0x000001BB时才存在,其组成字段顺序如下所示:
streamid类型:
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字节为单位;
LOOP End;
目前的系统头部好像是没有用到的,所以对于系统头部的解析,我们一般只要先首先判断是否存在系统头(根据系统头的起始码0x000001BB),然后我们读取系统头的头部长度,即header_length部分,然后根据系统头部的长度,跳过PS系统头,进入下一个部分,即PS的payload,PES包;在固定包头和系统头之后,就是PS包的payload,即PES包;若PSM存在,则第一个PES包即为PSM。
system_audio_lock_flag =1时
SCASR=(system_clock_frequency) / audio_sample_rate_in_the_P-STD
PSM Header
PSM提供了对PS流中的原始流和他们之间的相互关系的描述信息;PSM是作为一个PES分组出现,当stream_id == 0xBC时,说明此PES包是一个PSM;PSM是紧跟在系统头部后面的;PSM是作为PS包的payload存在的;
PSM由很多字段组成,其字节顺序如下所示:
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的长度的;
LOOP:
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字段里面的;
LOOP End;
CRC_32:CRC字段,占位32bit,CRC校验值;
stream_id类型定义
Pes header
6 Bytes 固定的 pes header 结构如下:
optionl pes header 中与 pts、dts 有关的结构如下:
Pes payload
基本流打包,每个 es packet 包含一个完整的音频帧或视频帧。
h264 es packet
h264 es packet 采用 Annex-B 格式封装码流,即使用 0x000001 作为 start code。
注意:
封 h264 es packet 不用添加 AUD
每个 es packet 应该包含一整帧的所有 slices
sps、pps、idr 属于一帧,需要封装到一个 es packet 里面,使用同一个 pes header 时间戳
aac es packet
aac es packet 由 28 bit 的 adts_fixed_header 和 28 bit 的 adts_variable_header 组成,共 7 bytes。
adts_fixed_header 结构如下(共 28 bit):
adts_variable_header 结构如下(共 28 bit):
PS总结:
解析PS包,要先找到PS包的的起始码0x000001BA位串,然后解析出系统头部字段,之后进入PS包的负载,判断是否有PSM,根据PSM确定payload的PES包中所负载的ES流类型;然后再根据ES流类型和ID从PES包中解析出具体的ES流;解包过程则相反;若要从PS流中找出来帧类型,必须将PS包解析成ES并组成完整的帧,然后在帧数据开始根据NAL头来进行帧的类型的判断;
3.2 Wireshark分析PS流(h264)
基于 GB28181 从海康 IPC 摄像头获取 PS 流,本篇文章结合实例,用于记录分析推送过来的 PS 流的内容及结构。
通过国标gb28181协议接收到海康的 RTP包(ps流),信源SSRC=0X55667788,seq从0开始,PT类型为PS流(PT 96)。
RTP包payload具体内容如下:
Payload具体内容解析如下:
PS Header:
起始 00 00 01 ba 代表PS包的开始。接下来跳过 9 个字节,暂时不关心它的内容,看第 10 个字节 fe,对应着二进制数据的 1111 1110,后三位为110 为十进制的 6,即六个字节是扩展内容(ff ff 00 c6 4b 56)。
PS System Header:
起始 00 00 01 bb代表System Header包的开始。00 12两个字节表示 System Header 的长度,即18 个字节(82 68 … e0 80)。在System Header 中,可以获取到 PS 中的码流种类(system_id)。header_length后的 6 个字节的数据之后,即 stream_id 字段(即e0)。它是 3 个字节一循环,下一个循环 system_id 字节值为 c0,为音频流,之后又出现了 bd ,bf,共四个循环streamid。
抓包分析,海康在系统头中定义了四个streamid,分别是0xe0,0xc0,0xbd,0xbf,前两个对应着视音频,后两个海康的的私有数据。
这个 bd或者bf 由文章有提及:
遇到00 00 01 bd的,这个是海康私有流的标识,可以丢弃。丢弃之后就看不到原视频里移动侦测时闪烁的红框。
参考:https://blog.csdn.net/chen495810242/article/details/39207305
参考:https://www.cnblogs.com/heiche/p/6895306.html
Program Stream Map(PSM):节目映射流
起始00 00 01 bc 代表Program Stream Map包的开始。之后的两个字段为 header 的长度,00 6a十进制的 106。
接下来的 106个字节也是属于 PSM段的[ee ff ******* e8 87],跳过两个字段的固定内容,就到了两个字节的 program_stream_info_length,其值为 00 24,说明其后跟着的 descriptor 共占 36 字节[40 0e ******* 0e 0f ]
接下来的值 00 3c 为 element_stream_map_length(基本流映射长度),也就是60字节,它表示接下来的 60字节都是用来描述原始流信息的,[1b e0 ******* 03 ff],接下来四个字节的CRC [ac fd e8 87]。
第一个字节 stream_type 值为 1b ,其后一个字节 e0 表示其为视频流,如图gb28181中定义。紧接着两个字节 00 28表示接下来描述占用 40 字节[42 0e … f0 87]。同理,接下来的字段 90 和 c0,代表着其为 G.711 编码的音频流。紧接着两个字节12个字节。
PES 包(Header + Body)
PSM 之后跟着的就是PES,它分为两个部分,一部分是 Header,一部分是 Payload,Header 用于存储一些描述信息,而 Body Payload 部分为其存储的原始数据,PES 可能有多个。
PES Header
数据开始以 00 00 01 进行分隔,接下来的一个字段决定着数据段的类型,其值为 e0 ,表示视频流。接下来两个字节 00 26 表示38字节数据内容。根据上图标记它们都是以 00 00 01 e0 起始的 PES 包,PES 是可以存在多个的,最后一个pes,两个字节ff c6 即 65478 个字节,根据长度位置后边的第二个字节 00 (二进制:0000 0000)判断可知,这个 PES 包的 PTS_DTS_flags 值为 0,这个 PES 包不包含 PTS 和 DTS 信息。其后两个字节忽略,接下来一个字节 07,也就是其后字节的长度为7字节,通过这个可以分割PES头部内容和数据段,其它字段暂不关心。
PES Body(H264 Payload)
PES Header 其后就是 Payload 数据,00 00 00 01 表示数据段的开始。本例中共有四个 PES 包,查看一下它们的数据部分。00 00 00 01 其后分别跟着 67、68,06,65。
从海康收到的第一个PS包,到最后解析到 I 帧的时候,至数据末尾肯定不到 1400 个字节,而最后一个 PES 包的长度字段标志为 65478 字节,可以确定的是因为 I 帧过大,进行了分包。
根据 H264 封装规则,67 代表这一帧为 SPS,68 为 PPS,06 为 SEI Message,65 为 I 帧。
H264在网络中传输的基本单元为NALU,NALU结构:NALU header(1byte) + NALU payload。NALU header 部分可以判断类型等信息。
8 = header & 0x1F PPS
7 = header & 0x1F SPS
6 = header & 0x1F SEI
5 = header & 0x1F I帧
1 = header & 0x1F P帧
3.3 Wireshark分析PS流(h265)
对于h265的ps流,与ps h264流大同小异,简单分析下:
ps header
system header
ps map
e0表示视频流,24表示h265类型
pes header
pes body
H265帧类型与H264不一样,其位置在第一个字节的1~6位(buf[0]&0x7E>>1),常见的NALU类型:
40 >> 01,type=32,VPS(视频参数集)
42 >> 01,type=33,SPS(序列参数集)
44 >> 01,type=34,PPS(图像参数及)
4E >> 01, type=39,SEI(补充增强信息)
26 >> 01,type=19,可能有RADL图像的IDR图像的SS编码数据 IDR
02 >>01, type=01,被参考的后置图像,且非TSA、非STSA的SS编码数据
VPS
SPS
PPS
SEI
IDR
四、ps流封装h264
该实现以最简单实例进行封装ps流,其中扩展字段、可选字段都是为空,不填,因此采用ps包结构最小长度分别
PS_HDR_LEN 14
SYS_HDR_LEN 18
PSM_HDR_LEN 24
PES_HDR_LEN 19
RTP_HDR_LEN 12
gb28181_header_maker.h
#pragma once
#include "bits.h"
#define PS_HDR_LEN 14
#define SYS_HDR_LEN 18
#define PSM_HDR_LEN 24
#define PES_HDR_LEN 19
#define RTP_HDR_LEN 12
#define RTP_PKT_END 1
#define RTP_PKT_APPENDING 0
int gb28181_make_ps_header(char *pData, int64_t s64Scr);
int gb28181_make_sys_header(char *pData, int audioCnt);
int gb28181_make_psm_header(char *pData, uint64_t videoType=0x1B, uint64_t audioType = 0x90);
int gb28181_make_pes_header(char *pData, int stream_id, int payload_len, int64_t pts, int64_t dts);
int gb28181_make_rtp_header(char *pData, int seqNum, int64_t timestamp, int ssrc, int isEnd);
gb28181_header_maker.cpp
#pragma once
#include "gb28181_header_maker.h"
#include <memory.h>
#include "bits.h"
int gb28181_make_ps_header(char *pData, int64_t s64Scr)
{
int64_t lScrExt = 0; //(s64Scr) % 100;
// s64Scr = s64Scr * 3600; // / 100; // 90000/fps
// 这里除以100是由于sdp协议返回的video的频率是90000,帧率是25帧/s,所以每次递增的量是3600,
// 所以实际你应该根据你自己编码里的时间戳来处理以保证时间戳的增量为3600即可,
//如果这里不对的话,就可能导致卡顿现象了
bits_buffer_t bitsBuffer;
bitsBuffer.i_size = PS_HDR_LEN;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80; // 二进g制:10000000 这里是为了后面对一个字节的每一位进行操作,避免大小端夸字节字序错乱
bitsBuffer.p_data = (unsigned char *)(pData);
memset(bitsBuffer.p_data, 0, PS_HDR_LEN);
bits_write(&bitsBuffer, 32, 0x000001BA); /*start codes*/
bits_write(&bitsBuffer, 2, 1); /*marker bits '01b'*/
bits_write(&bitsBuffer, 3, (s64Scr >> 30) & 0x07); /*System clock [32..30] system_clock_reference_base*/
bits_write(&bitsBuffer, 1, 1); /*marker bit*/
bits_write(&bitsBuffer, 15, (s64Scr >> 15) & 0x7FFF);/*System clock [29..15] system_clock_reference_base*/
bits_write(&bitsBuffer, 1, 1); /*marker bit*/
bits_write(&bitsBuffer, 15, s64Scr & 0x7fff); /*System clock [0..14] system_clock_reference_base*/
bits_write(&bitsBuffer, 1, 1); /*marker bit*/
bits_write(&bitsBuffer, 9, lScrExt & 0x01ff); /*System clock system_clock_reference_extension*/
bits_write(&bitsBuffer, 1, 1); /*marker bit*/
bits_write(&bitsBuffer, 22, (3750) & 0x3fffff); /*bit rate(n units of 50 bytes per second.) program_mux_rate*/
bits_write(&bitsBuffer, 2, 3); /*marker bits '11'*/
bits_write(&bitsBuffer, 5, 0x1f); /*reserved(reserved for future use)*/
bits_write(&bitsBuffer, 3, 0); /*stuffing length*/
return 0;
}
int gb28181_make_sys_header(char *pData, int audioCnt)
{
bits_buffer_t bitsBuffer;
bitsBuffer.i_size = SYS_HDR_LEN;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char *)(pData);
memset(bitsBuffer.p_data, 0, SYS_HDR_LEN);
/*system header*/
bits_write(&bitsBuffer, 32, 0x000001BB); /*start code*/
bits_write(&bitsBuffer, 16, SYS_HDR_LEN - 6);/*header_length 表示次字节后面的长度,后面的相关头也是次意思*/
bits_write(&bitsBuffer, 1, 1); /*marker_bit*/
bits_write(&bitsBuffer, 22, 3967); /*rate_bound*/
bits_write(&bitsBuffer, 1, 1); /*marker_bit*/
bits_write(&bitsBuffer, 6, audioCnt); /*audio_bound*/
bits_write(&bitsBuffer, 1, 0); /*fixed_flag */
bits_write(&bitsBuffer, 1, 1); /*CSPS_flag */
bits_write(&bitsBuffer, 1, 1); /*system_audio_lock_flag*/
bits_write(&bitsBuffer, 1, 1); /*system_video_lock_flag*/
bits_write(&bitsBuffer, 1, 1); /*marker_bit*/
bits_write(&bitsBuffer, 5, 1); /*video_bound*/
bits_write(&bitsBuffer, 1, 0); /*dif from mpeg1*/
bits_write(&bitsBuffer, 7, 0x7F); /*reserver*/
/*video stream bound*/
bits_write(&bitsBuffer, 8, 0xE0); /*stream_id*/
bits_write(&bitsBuffer, 2, 3); /*marker_bit */
bits_write(&bitsBuffer, 1, 1); /*PSTD_buffer_bound_scale*/
bits_write(&bitsBuffer, 13, 2048); /*PSTD_buffer_size_bound*/
/*audio stream bound*/
bits_write(&bitsBuffer, 8, 0xC0); /*stream_id*/
bits_write(&bitsBuffer, 2, 3); /*marker_bit */
bits_write(&bitsBuffer, 1, 0); /*PSTD_buffer_bound_scale*/
bits_write(&bitsBuffer, 13, 512); /*PSTD_buffer_size_bound*/
return 0;
}
int gb28181_make_psm_header(char *pData, uint64_t videoType, uint64_t audioType)
{
bits_buffer_t bitsBuffer;
bitsBuffer.i_size = PSM_HDR_LEN;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char *)(pData);
memset(bitsBuffer.p_data, 0, PSM_HDR_LEN);
bits_write(&bitsBuffer, 24, 0x000001); /*start code*/
bits_write(&bitsBuffer, 8, 0xBC); /*map stream id*/
bits_write(&bitsBuffer, 16, 18); /*program stream map length 16+8=24*/
bits_write(&bitsBuffer, 1, 1); /*current next indicator */
bits_write(&bitsBuffer, 2, 3); /*reserved*/
//bits_write(&bitsBuffer, 5, 0); /*program stream map version*/
bits_write(&bitsBuffer, 5, 1); /*program stream map version*/
bits_write(&bitsBuffer, 7, 0x7F); /*reserved */
bits_write(&bitsBuffer, 1, 1); /*marker bit */
bits_write(&bitsBuffer, 16, 0); /*programe stream info length is 0*/
bits_write(&bitsBuffer, 16, 8); /*elementary stream map length is 8*/
/*video*/
//bits_write(&bitsBuffer, 8, 0x1B); /*stream_type h264*/
bits_write(&bitsBuffer, 8, videoType); /*stream_type h264*/
bits_write(&bitsBuffer, 8, 0xE0); /*elementary_stream_id*/
bits_write(&bitsBuffer, 16, 0); /*elementary_stream_info_length */
/*audio*/
//bits_write(&bitsBuffer, 8, 0x90); /*stream_type g711*/
bits_write(&bitsBuffer, 8, audioType); /*stream_type g711*/
bits_write(&bitsBuffer, 8, 0xC0); /*elementary_stream_id*/
bits_write(&bitsBuffer, 16, 0); /*elementary_stream_info_length is*/
/*crc (2e b9 0f 3d)*/
bits_write(&bitsBuffer, 8, 0x45); /*crc (24~31) bits*/
bits_write(&bitsBuffer, 8, 0xBD); /*crc (16~23) bits*/
bits_write(&bitsBuffer, 8, 0xDC); /*crc (8~15) bits*/
bits_write(&bitsBuffer, 8, 0xF4); /*crc (0~7) bits*/
return 0;
}
int gb28181_make_pes_header(char *pData, int stream_id, int payload_len, int64_t pts, int64_t dts)
{
bits_buffer_t bitsBuffer;
bitsBuffer.i_size = PES_HDR_LEN;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char *)(pData);
memset(bitsBuffer.p_data, 0, PES_HDR_LEN);
bits_write(&bitsBuffer, 24, 0x000001); //*start code*//*
bits_write(&bitsBuffer, 8, (stream_id)); //*streamID*//*
bits_write(&bitsBuffer, 16, (payload_len)+13); //*packet_len*//* //指出pes分组中数据长度和该字节后的长度和
bits_write(&bitsBuffer, 2, 2); //*'10'*//*
bits_write(&bitsBuffer, 2, 0); //*scrambling_control*//*
bits_write(&bitsBuffer, 1, 0); //*priority*//*
bits_write(&bitsBuffer, 1, 0); //*data_alignment_indicator*//*
bits_write(&bitsBuffer, 1, 0); //*copyright*//*
bits_write(&bitsBuffer, 1, 0); //*original_or_copy*//*
bits_write(&bitsBuffer, 1, 1); //*PTS_flag*//*
bits_write(&bitsBuffer, 1, 1); //*DTS_flag*//*
bits_write(&bitsBuffer, 1, 0); //*ESCR_flag*//*
bits_write(&bitsBuffer, 1, 0); //*ES_rate_flag*//*
bits_write(&bitsBuffer, 1, 0); //*DSM_trick_mode_flag*//*
bits_write(&bitsBuffer, 1, 0); //*additional_copy_info_flag*//*
bits_write(&bitsBuffer, 1, 0); //*PES_CRC_flag*//*
bits_write(&bitsBuffer, 1, 0); //*PES_extension_flag*//*
bits_write(&bitsBuffer, 8, 10); //*header_data_length*//*
bits_write(&bitsBuffer, 4, 3); //*'0011'*//*
bits_write(&bitsBuffer, 3, ((pts) >> 30) & 0x07); //*PTS[32..30]*//*
bits_write(&bitsBuffer, 1, 1);
bits_write(&bitsBuffer, 15, ((pts) >> 15) & 0x7FFF); //*PTS[29..15]*//*
bits_write(&bitsBuffer, 1, 1);
bits_write(&bitsBuffer, 15, (pts) & 0x7FFF); //*PTS[14..0]*//*
bits_write(&bitsBuffer, 1, 1);
bits_write(&bitsBuffer, 4, 1); //*'0001'*//*
bits_write(&bitsBuffer, 3, ((dts) >> 30) & 0x07); //*DTS[32..30]*//*
bits_write(&bitsBuffer, 1, 1);
bits_write(&bitsBuffer, 15, ((dts) >> 15) & 0x7FFF); //*DTS[29..15]*//*
bits_write(&bitsBuffer, 1, 1);
bits_write(&bitsBuffer, 15, (dts) & 0x7FFF); //*DTS[14..0]*//*
bits_write(&bitsBuffer, 1, 1);
return 0;
}
int gb28181_make_rtp_header(char *pData, int seqNum, int64_t timestamp, int ssrc, int isEnd)
{
bits_buffer_t bitsBuffer;
bitsBuffer.i_size = RTP_HDR_LEN;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char *)(pData);
memset(bitsBuffer.p_data, 0, RTP_HDR_LEN);
bits_write(&bitsBuffer, 2, 2); /*协议版本*/
bits_write(&bitsBuffer, 1, 0); /*P*/
bits_write(&bitsBuffer, 1, 0); /*X*/
bits_write(&bitsBuffer, 4, 0); /*CSRC个数*/
bits_write(&bitsBuffer, 1, isEnd); /*一帧是否结束*/
bits_write(&bitsBuffer, 7, 96); /*载荷的数据类型*/
bits_write(&bitsBuffer, 16, seqNum); /*序列号,第几个*/
bits_write(&bitsBuffer, 32, timestamp); /*时间戳,第一个 */
bits_write(&bitsBuffer, 32, ssrc); /*同步信源(SSRC)标识符*/
return 0;
}
推流流程
#include <iostream>
#include <vector>
#include <sstream>
#include <string>
#include "gb28181_header_maker.h"
std::vector<Nalu*> h264_nalus; //读取h264nalu放到vector中
typedef enum {
NALU_TYPE_SLICE = 1,
NALU_TYPE_DPA = 2,
NALU_TYPE_DPB = 3,
NALU_TYPE_DPC = 4,
NALU_TYPE_IDR = 5,
NALU_TYPE_SEI = 6,
NALU_TYPE_SPS = 7,
NALU_TYPE_PPS = 8,
NALU_TYPE_AUD = 9,
NALU_TYPE_EOSEQ = 10,
NALU_TYPE_EOSTREAM = 11,
NALU_TYPE_FILL = 12,
}NaluType;
class Nalu {
public:
char * packet;
int length;
NaluType type;
~Nalu() {
if (packet != nullptr) {
delete packet;
packet = nullptr;
}
}
};
void push_ps_stream() {
char ps_header[PS_HDR_LEN];
char ps_system_header[SYS_HDR_LEN];
char ps_map_header[PSM_HDR_LEN];
char pes_header[PES_HDR_LEN];
char rtp_header[RTP_HDR_LEN];
int time_base = 90000; //时间基
int fps = 25;
int send_packet_interval = 1000 / fps; //40ms
int interval = time_base / fps; //3600 timestamp
long pts = 0;
char frame[1024 * 128]='\0';
int single_packet_max_length = 1400; //rtp最大长度
char rtp_packet[RTP_HDR_LEN + 1400];
int ssrc = 0xffffffff;
int rtp_seq = 0;
size_t size_264 = h264_nalus.size(); //读取视频文件后,存储h264帧的个数,用于循环发送视频
while (is_running_to_push_stream){
for (int i = 0; i < size_264; i++) {
if (!is_running_to_push_stream) {
break;
}
Nalu* nalu = h264_nalus.at(i);
NaluType type = nalu->type;
int length = nalu->length;
char* packet = nalu->packet;
int index = 0;
if (NALU_TYPE_IDR == type) { //如果关键帧IDR,那么组包ps header+system header+psm header
gb28181_make_ps_header(ps_header, pts);
memcpy(frame, ps_header, PS_HDR_LEN);
index += PS_HDR_LEN;
gb28181_make_sys_header(ps_system_header, 0x3f);
memcpy(frame + index, ps_system_header, SYS_HDR_LEN);
index += SYS_HDR_LEN;
gb28181_make_psm_header(ps_map_header);
memcpy(frame + index, ps_map_header, PSM_HDR_LEN);
index += PSM_HDR_LEN;
}
else { //如果非关键帧IDR,那么组包ps header
gb28181_make_ps_header(ps_header, pts);
memcpy(frame, ps_header, PS_HDR_LEN);
index += PS_HDR_LEN;
}
//组装pes header
gb28181_make_pes_header(pes_header, 0xe0, length, pts, pts);
memcpy(frame + index, pes_header, PES_HDR_LEN);
index += PES_HDR_LEN;
//组装pes body即包数据
memcpy(frame + index, packet, length);
index += length;
//发送rtp包,如果rtp包长度》1400分包发送,但是pts不变,仅仅sequence++
int rtp_packet_count = ((index - 1) / single_packet_max_length) + 1;
for (int i = 0; i < rtp_packet_count; i++) {
gb28181_make_rtp_header(rtp_header, rtp_seq, pts, ssrc, i == (rtp_packet_count - 1));
int writed_count = single_packet_max_length;
if ((i + 1) * single_packet_max_length > index) {
writed_count = index - (i * single_packet_max_length);
}
int rtp_start_index = 0;
unsigned short rtp_packet_length = RTP_HDR_LEN + writed_count;
if (is_tcp) {
unsigned char packt_length_ary[2];
packt_length_ary[0] = (rtp_packet_length >> 8) & 0xff;
packt_length_ary[1] = rtp_packet_length & 0xff;
memcpy(rtp_packet, packt_length_ary, 2);
rtp_start_index = 2;
}
memcpy(rtp_packet + rtp_start_index, rtp_header, RTP_HDR_LEN);
memcpy(rtp_packet + rtp_start_index + RTP_HDR_LEN, frame + (i * single_packet_max_length), writed_count);
rtp_seq++;
send_packet(target_ip, target_port, rtp_packet, rtp_start_index + rtp_packet_length); //自己实现一个发送包的接口
}
pts += interval;
ULONGLONG end_time = GetTickCount64();
ULONGLONG dis = end_time - start_time;
if (dis < send_packet_interval) {
std::this_thread::sleep_for(std::chrono::milliseconds(send_packet_interval -dis));
}
//std::this_thread::sleep_for(std::chrono::milliseconds(38));
}
}
}
五、总结
本文实现了最简单的ps流封装,以及通过rtp发送ps流的过程。希望你能够有所学习把。
源码可以通过连接()下载
参考:https://blog.csdn.net/chenxijie1985/article/details/118890318
参考:https://www.cnblogs.com/moonwalk/p/16200434.html