技术在于交流、沟通,转载请注明出处并保持作品的完整性。

原文:https://blog.csdn.net/hiwubihe/article/details/80736848

[本系列相关文章]

  • H264和音频流打包成PS流 (MPEG2-PS)
  • PS流解复用成H264和音频流(ES提取)
  • H264和音频流打包成TS流 (MPEG2-TS)
  • TS流解复用成H264和音频流(ES提取)
  • FLV文件格式基础
  • 解复用FLV文件(基于FFMPEG解析FLV(h264+aac))
  • 解复用FLV文件(不用FFMPEG,C++实现)

在诸如安防系统标准GB28181和“电网视频监控系统及接口”中,固定视频流的传输格式为PS格式。PS流和TS流的概念是在MPEG2的ISO/IEC-13818标准的第一部分“系统”中提出的。其提出的目的是提供MPEG2编码比特的存储与传输方案。TS流在后面博文中阐述,本文将基于C/C++提供一个PS流的打包库PsMuxer.dll,并提供DEMO测试程序。打包程序包括一个PsMuxerDemo和PsMuxer库,文档包括主要参考的几篇文章"iso13818-1.pdf" "PS流和TS流介绍.docx","视音频数据PS封装-offset.doc"。

先介绍PS封装中的基本元素

  1. ES 音视频采集编码后等待打包PS的单元,H264就是一个I帧,P帧或者其他H264辅助信息。音频的打包方式都是一样的,目前PsMuxer.dll只支持H264和各种音频的ES流,本来打算添加H265和MPEG4-PART2的,考虑到H265并不是iso13818-1规定的类型,MPEG4-PART2打包和H264基本相同,H265只是标准扩展的,所以厂商实现不统一,就去掉这部分内容,后期需要实现可以添加。标准支持情况在Table 2-29中有表述
  2. PES 编码层之上的传输或者存储单元,一个PS包里面可以有多个PES包,这些PES可以是一路流的多个单元(如I帧太大超过标准的65535,只能分包),也可以是多路流的多个单元,如PS包中包含一个PES视频包和一个PES音频包。解复用PS流主要只有PES包里面有ES。PES头包括类别码,和PTS,DTS信息。
  3. PS包头 表示一个PS包的开始。一般来说一个节目流PS只包括一个节目流,一个节目流中有多路流,如视频流1,视频流2,音频流,解复用PS流主要依据每一路流stream_id区别。解复用把不同stream_id的流分别做不同处理,如在不同屏幕显示等。PS头部主要包括比特率,注意是以50B为单位的,PsMuxer.dll中处理方式为统计一段时间数据流量,然后填写,过一段时间重新统计下一段时间区间的平均比特率。PS头部还包括时钟参考信息,为当前PS段最后1B数据期望送入解码器的时间,和PES中的PTS不一样,但是PsMuxer.dll中简单处理,把视频流中PTS放入PS头部时钟参考,时间以90KHZ为单位。
  4. PS系统头,系统头 主要包括 头长度,视频流个数,音频流个数,及每一路流信息列举(stream_id,视频还是音频),stream_id怎么分配在下面讲解。
  5. PS MAP头 主要包括PS节目流包含的流信息,流类型,stream_id,流描述信息。这里的流类型很重要,解码器需要根据流类型解码,H264码流需要赋值PSMUX_ST_VIDEO_H264,如果赋值成PSMUX_ST_VIDEO_MPEG4,交给VLC是播放不了的。

PS支持复用音视频类型

//流类型 /* Table 2-29 in spec */
enum PsMuxStreamType
{ PSMUX_ST_RESERVED                   = 0x00,PSMUX_ST_VIDEO_MPEG1                = 0x01,PSMUX_ST_VIDEO_MPEG2                = 0x02,PSMUX_ST_AUDIO_MPEG1                = 0x03,PSMUX_ST_AUDIO_MPEG2                = 0x04,PSMUX_ST_PRIVATE_SECTIONS           = 0x05,PSMUX_ST_PRIVATE_DATA               = 0x06,PSMUX_ST_MHEG                       = 0x07,PSMUX_ST_DSMCC                      = 0x08,PSMUX_ST_H222_1                     = 0x09,/* later extensions */PSMUX_ST_AUDIO_AAC                  = 0x0f,PSMUX_ST_VIDEO_MPEG4                = 0x10,PSMUX_ST_VIDEO_H264                 = 0x1b,/* private stream types */PSMUX_ST_PS_AUDIO_AC3               = 0x81,PSMUX_ST_PS_AUDIO_DTS               = 0x8a,PSMUX_ST_PS_AUDIO_LPCM              = 0x8b,PSMUX_ST_PS_AUDIO_G711A             = 0x90,PSMUX_ST_PS_AUDIO_G711U             = 0x91,PSMUX_ST_PS_AUDIO_G722_1            = 0x92,PSMUX_ST_PS_AUDIO_G723_1            = 0x93,PSMUX_ST_PS_AUDIO_G729              = 0x99,PSMUX_ST_PS_AUDIO_SVAC              = 0x9b,PSMUX_ST_PS_DVD_SUBPICTURE          = 0xff,//下面定义不是标准里面定义的/* Non-standard definitions */PSMUX_ST_VIDEO_DIRAC                = 0xD1
};

PS流中stream_id怎么分配的,主要依据类型,如视频流从0XE0分配到0XEF,最多可以包含16路视频流,具体定义如下:

//视频流stream_id分配初始化值
#define PSMUX_STREAM_ID_MPGA_INIT       0xc0
//视频流stream_id分配最大值
#define PSMUX_STREAM_ID_MPGA_MAX        0xcf//音频流stream_id分配初始化值
#define PSMUX_STREAM_ID_MPGV_INIT       0xe0
//音频流stream_id分配最大值
#define PSMUX_STREAM_ID_MPGV_MAX        0xef#define PSMUX_STREAM_ID_AC3_INIT        0x80
#define PSMUX_STREAM_ID_AC3_MAX         0x87#define PSMUX_STREAM_ID_SPU_INIT        0x20
#define PSMUX_STREAM_ID_SPU_MAX         0x3f#define PSMUX_STREAM_ID_DTS_INIT        0x88
#define PSMUX_STREAM_ID_DTS_MAX         0x8f#define PSMUX_STREAM_ID_LPCM_INIT       0xa0
#define PSMUX_STREAM_ID_LPCM_MAX        0xaf#define PSMUX_STREAM_ID_DIRAC_INIT      0x60
#define PSMUX_STREAM_ID_DIRAC_MAX       0x6f

PS封装H264流程

I帧封装 PS header | PS system header | PS system Map | PES header |  h264 raw data,每个IDR NALU前一般都会包含SPSPPSNALU,因此将SPSPPSIDR NALU封装为一个PS包,包括ps头,然后加上PS system headerPS system mapPES header+h264 rawdata

P帧封装 PS header | PES header | h264 raw data 非关键帧的PS包,直接加上PS头和PES头就可以了

音频封装 当有音频数据时,将数据加上PES header 放到视频PES后就可以了。顺序如下:PS=PS|PES(video)|PES(audio)

PS封装有不懂可以参照上面文档介绍。

PS复用库PsMuxer.dll的头文件

 
/*******************************************************************************
Copyright (c) wubihe Tech. Co., Ltd. All rights reserved.
--------------------------------------------------------------------------------Date Created:   2014-10-25
Author:         wubihe
Description:    PS流封装库头文件--------------------------------------------------------------------------------
Modification History
DATE          AUTHOR          DESCRIPTION
--------------------------------------------------------------------------------********************************************************************************/
#ifndef IPSMUXER_H_
#define IPSMUXER_H_#ifdef WIN32
#include <windows.h>
#include <windef.h>
#ifdef PSMUXER_EXPORTS
#define DLLEXPORT __declspec(dllexport)
//#define   DLLEXPORT
#else
#define DLLEXPORT
#endif
#else
#define DLLEXPORT
#define WINAPI
#endif //WIN32#include <string>
///
#ifdef __cplusplus
extern "C"
{
#endif/******************************************************************************
PsMuxer.dll宏定义
*******************************************************************************//******************************************************************************
PsMuxer.dll错误码定义,HPsMuxer.dll库错误码的范围:0-255
*******************************************************************************//******************************************************************************
HPlayer.dll数据结构定义
*******************************************************************************/
///日志级别类型
typedef enum _PM_LOG_LEVEL
{PM_LOG_TRACE =     0,PM_LOG_DEBUG =        1,                      PM_LOG_INFO  =      2,                      PM_LOG_WARN  =      3,                      PM_LOG_ERROR =      4, PM_LOG_FATAL =       5
} PM_LOG_LEVEL;//复合流类型
typedef enum _PM_STREAM_TYPE
{MUXSER_VIDEO_TYPE_H264 =       0,MUXSER_VIDEO_TYPE_H265    =       1,                      MUXSER_AUDIO_TYPE_G711A =       2,   MUXSER_AUDIO_TYPE_AAC  =       3,MUXSER_SUPPORT_NUM        =       4
} PM_STREAM_TYPE;//帧转换结果类型
typedef enum _PM_RESULT_TYPE
{//帧换成功PM_RESULT_OK =       0,//SPS/PPS/SEI类型数据转换时,转换成功返回此值,但是没有输出信息,必须一直送入IDR才有输出PM_RESULT_WAIT   =       1, //送入转换器数据非法PM_RESULT_FRAME   =       2,//转换出错,如缓存不够等PM_RESULT_ERROR =     3
} PM_RESULT_TYPE;//帧信息
typedef struct tagPmFrameInfo
{//帧数据unsigned char * pFrame        ;//帧大小int               iFrameLen   ;//帧PTSLONG64           lPts        ; //帧DTSLONG64          lDts        ;
} PmFrameInfo;/****************************************************************************General Callback通用回调接口
****************************************************************************/
typedef void(CALLBACK *PM_LogCBFun)(PM_LOG_LEVEL nLogLevel, const char *szMessage, void* pUserData );  /****************************************************************************General System Interface通用系统接口
****************************************************************************//**************************************************************************
* Function Name  : PM_SetLogCallBack
* Description    : 设置库日志回调
* Parameters     : pLogFunc (日志回调函数)
* Parameters     : pUserData(日志回调用户数据)
* Return Type    : void
* Last Modified  : wubihe
***************************************************************************/DLLEXPORT void  WINAPI PM_SetLogCallBack(PM_LogCBFun pLogFunc,  void* pUserData);/**************************************************************************
* Function Name  : PM_CreateMuxHandle
* Description    : 创建PS流复用器句柄
* Parameters     :
* Return Type    : int >1为合法句柄,<=0非法 最大支持299路
* Last Modified  : wubihe
***************************************************************************/
DLLEXPORT int  WINAPI  PM_CreateMuxHandle();/**************************************************************************
* Function Name  : PM_AddStream
* Description    : PS流复用器中添加流
* Parameters     : iHandle 复用器句柄
* Parameters     : eType   流类型句柄
* Return Type    : int 流句柄 >=0 为合法句柄,<0非法 最大支持2路
* Last Modified  : wubihe
***************************************************************************/
DLLEXPORT int  WINAPI  PM_AddStream(int iHandle,    PM_STREAM_TYPE eType);/**************************************************************************
* Function Name  : PM_FrameTrans
* Description    : PS流复用器复用视频帧
* Parameters     : iHandle      复用器句柄
* Parameters     : iStream      流句柄
* Parameters     : stFrameInfo  帧信息
* Parameters     : outBuf       输出缓存
* Parameters     : pOutSize     输入为输出缓存大小,输出为实际封装后数据大小
* Return Type    : PM_RESULT_TYPE
* Last Modified  : wubihe
***************************************************************************/
DLLEXPORT PM_RESULT_TYPE  WINAPI PM_FrameTrans(int iHandle, int iStream, PmFrameInfo stFrameInfo,unsigned char * outBuf, int* pOutSize);/**************************************************************************
* Function Name  : PM_DestroyMuxHandle
* Description    : PS流复用器销毁
* Parameters     : iHandle      复用器句柄
* Return Type    : void
* Last Modified  : wubihe
***************************************************************************/
DLLEXPORT void WINAPI  PM_DestroyMuxHandle(int iHandle);
#ifdef __cplusplus
}
#endif#endif /* IPSMUXER_H_ */

PS复用库PsMuxer.dll的调用流程

PS复用库PsMuxer.dll调用Demo

/*******************************************************************************
Copyright (c) wubihe Tech. Co., Ltd. All rights reserved.
--------------------------------------------------------------------------------Date Created:   2014-10-25
Author:         wubihe QQ:1269122125 Email:1269122125@qq.com
Description:    PS流封装库PsMuxer使用Demo 试用版本Demo只能运行240S 需要授权库或者
全部源码请联系作者
--------------------------------------------------------------------------------
Modification History
DATE          AUTHOR          DESCRIPTION
--------------------------------------------------------------------------------********************************************************************************/
#include "excpt.h"
#include "TIOBuffer.h"
#include "IPsMuxer.h"#define  MAX_BUFFER_SIZE     (1024*8)
#define  MAX_OUT_BUFFER_SIZE (1024*1024)static FILE *gInputFile = NULL;
static FILE *gOutputFile = NULL;//读取H264数据缓存
unsigned char       gszReadBuffer[MAX_BUFFER_SIZE];
//解析h264数据缓存
comn::IOBuffer      gH264ParserBuff;struct NaluPacket
{unsigned char*  data;int               length;int              prefix;
};//帧类型定义
enum NAL_type
{NAL_IDR,NAL_SPS,NAL_PPS,NAL_SEI,NAL_PFRAME,NAL_VPS,NAL_SEI_PREFIX,NAL_SEI_SUFFIX,NAL_other,NAL_TYPE_NUM
};unsigned char*    g_pMuxBuf;
long            g_Pts = 0;
long            g_Dts = 0;//判断是否是264或者265帧,如果是顺便把NalTypeChar设置一下
bool isH264Or265Frame(unsigned char* buf, unsigned char* NalTypeChar,int *iHeadLen)
{bool bOk = false;unsigned char c = 0;if (buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] == 1){if (NalTypeChar){*NalTypeChar = buf[4];}*iHeadLen = 4;bOk = true;}if (buf[0] == 0 && buf[1] == 0 && buf[2] == 1 ){if (NalTypeChar){*NalTypeChar = buf[3];}*iHeadLen = 3;bOk = true;}return bOk;
}NAL_type getH264NALtype(unsigned char c)
{switch(c & 0x1f){
case 6:return NAL_SEI;break;
case 7:return NAL_SPS;break;
case 8:return NAL_PPS;break;
case 5:return NAL_IDR;break;
case 1:return NAL_PFRAME;break;
default:return NAL_other;break;}return NAL_other;
}NAL_type getH265NALtype(unsigned char c)
{int type = (c & 0x7E)>>1;if(type == 33)return NAL_SPS;if(type == 34)return NAL_PPS;if(type == 32)return NAL_VPS;if(type == 39)return NAL_SEI_PREFIX;if(type == 40)return NAL_SEI_SUFFIX;if((type >= 1) && (type <=9))return NAL_PFRAME;if((type >= 16) && (type <=21))return NAL_IDR;return NAL_other;
}void ProcessData(int iHandle ,int iStream,unsigned char* buf, int len)
{PmFrameInfo stFrameInfo;stFrameInfo.pFrame     = buf;stFrameInfo.iFrameLen = len;stFrameInfo.lPts      = g_Pts;stFrameInfo.lDts        = g_Dts;int MuxOutSize = 0;PM_RESULT_TYPE eResult;int iOutSize = MAX_OUT_BUFFER_SIZE;eResult = PM_FrameTrans(iHandle, iStream, stFrameInfo,g_pMuxBuf, &iOutSize);if (eResult == PM_RESULT_OK && iOutSize > 0){fwrite(g_pMuxBuf, iOutSize, 1, gOutputFile);fflush(gOutputFile);}else if(eResult == PM_RESULT_WAIT){//printf("mux wait!\n");}else{printf("mux error !\n");}unsigned char c = 0;int iHeadLen;if (!isH264Or265Frame(buf, &c,&iHeadLen)){return;}NAL_type Type = getH264NALtype(c);if ((Type == NAL_IDR) || (Type == NAL_PFRAME)){//Pts递增25fps,0.04s一帧,时间9000HZ为单位,所以3600g_Pts += 3600;g_Dts += 3600;}
}bool findNalu(unsigned char* buffer, size_t length, size_t start, NaluPacket& packet)
{__try{if ((length < 3) || ((length - start) < 3)){return false;}bool     found   = false;unsigned char* p    = buffer;for (size_t i = start; i < (length - 3); ++ i){if ((p[i] == 0) && (p[i+1] == 0)){if (p[i+2] == 0){if (((i + 3) < length) && (p[i+3] == 1)){//0x 00 00 00 01 packet.data = p + i;packet.length = i;packet.prefix = 4;found = true;break;}}else if (p[i+2] == 1){packet.data = p + i;packet.length = i;packet.prefix = 3;found = true;break;}}}return found;}__except(EXCEPTION_EXECUTE_HANDLER){return false;}
}void show_usage(const char *name)
{printf("usage:\n");printf("  for test ps streaming: %s input_file\n", name);getchar();
}int main(int argc, char* argv[])
{if (argc != 2){show_usage(argv[0]);return 0;}char szOutFileName[256] = {0};sprintf(szOutFileName, "%s.mpeg", argv[1]);gInputFile = fopen(argv[1], "rb");if (!gInputFile){printf("read file failed!\n");return 0;}gOutputFile = fopen(szOutFileName, "wb");if (!gOutputFile){printf("open output file failed!\n");return 0;}g_pMuxBuf  = new unsigned char[MAX_OUT_BUFFER_SIZE];int iMuxHandle = PM_CreateMuxHandle();if(iMuxHandle <= 0){printf("PM_CreateMuxHandle Error!\n");return 0;}int iStreamId;iStreamId = PM_AddStream(iMuxHandle, MUXSER_VIDEO_TYPE_H264);if(iStreamId < 0){printf("PM_AddStream Error!\n");return 0;}int iReadSize = fread(gszReadBuffer, 1, MAX_BUFFER_SIZE, gInputFile);while(iReadSize > 0){gH264ParserBuff.write(gszReadBuffer, iReadSize);unsigned char *pBufferDataPtr;size_t   sBufferDataReadable;while (true){pBufferDataPtr      = gH264ParserBuff.getReadPtr();sBufferDataReadable = gH264ParserBuff.readable();NaluPacket firstPacket;if (!findNalu(pBufferDataPtr, sBufferDataReadable, 0, firstPacket)){break;}NaluPacket secondPacket;if (!findNalu(pBufferDataPtr, sBufferDataReadable, firstPacket.length + firstPacket.prefix, secondPacket)){break;}firstPacket.length = secondPacket.length - firstPacket.length;ProcessData(iMuxHandle ,iStreamId,firstPacket.data, firstPacket.length);gH264ParserBuff.skip(secondPacket.length);}//实际播放应该按照比特率播放iReadSize = fread(gszReadBuffer, 1, MAX_BUFFER_SIZE, gInputFile);}fclose(gInputFile);fclose(gOutputFile);printf("Ps file: %s Generate Success!\n",szOutFileName);printf("GetChar To Exit!\n");getchar();return 0;
}
 

DEMO使用方法

工程目录/bin/目录运行  PsMuxerDemo_T.exe huangdun.264 将生成PS文件huangdun.264.mpeg

运行结果码流分析,Elecard StreamAnalysis没有分析处理PS_MAP。

思考:

音视频流封装格式有很多,但是为什么只有PS或者TS适合网络传输呢?可能有其他原因,但是我理解的有一个原因应该是一般封装格式,你需要特殊的流化处理,这个可以在live555源码里面看到,ps是可以直接切断打包RTP传输的,即使你PS开始部分数据丢失,等到I帧来的时候,你又有PS系统头 PSMAP信息,你就知道当前流有多少stream,分别是什么格式编码,所以能解码播放。

MPEG2-PS对编码方式的支持情况:

编译环境:   Win7_64bit+VS2008

DEMO下载地址:https://download.csdn.net/download/hiwubihe/10487109

H264和音频流打包成PS流 (MPEG2-PS)相关推荐

  1. H264和音频流打包成TS流 (MPEG2-TS)

    技术在于交流.沟通,转载请注明出处并保持作品的完整性. 原文:https://blog.csdn.net/hiwubihe/article/details/80865920 [本系列相关文章] H26 ...

  2. ps流组成 PS封装 (PS+SYS+PSM+PES+RAW )概述

    在工作中,招标时要对设备进行gb28181检测,需要对音视频数据进行封装,封装方式为PS流,封装实现如下: 简要概述:整体机制均有udp发送,发送的数据要封装为rtp格式,rtp负载即为PS流. PS ...

  3. PES,TS,PS,RTP等流的打包格式解析之PS流

    本篇描述PS流的封装格式 1.PS头封装格式 PS流是对PES的进一步封装,是将具有共同时间基准的一个或多个PES包组合而成的单一的数据流:其基本单位是PS包,PS流由很多个PS包组成,PS包主要由固 ...

  4. Gb28181之Ps流解析H264

    gb28181发送码流选择PS流,PS流在封装H264的数据.本文详细描述如何通过ps流解析H264码流. *************************PSM流解析*************** ...

  5. h264 ps流vlc播放

    因项目需要,需要摄像机接入GB2818网关并通过rtp ps流传输视频流,整理一下这几天的技术点. 一.ps流封包 ps流打包中I帧和P帧打包略有差异:I帧:ps头+ps系统头+ps map+PES头 ...

  6. 流媒体基础知识TS流 PS流 ES流区别

    IP数据报有首部和数据两部分组成的,首部的前一部分是固定长度20字节,是所有IP数据报必须具有的.首部包括:总长度.标识.MF.DF.片偏移. 数字信号实际传送的是数据流,一般数据流包括以下三种: ( ...

  7. ES流、PES流、PS流和TS流介绍

    流媒体系统结构 整个传输过程为:音视频数据分别通过图像声音编码器打包成ES(elemental stream 基本数据流),在通过PES打包器打包成PES(packet elemental strea ...

  8. ps流 转发_一种国标PS流转RTMP直播流的实时转换方法与流程

    本发明属于视频技术领域,具体涉及一种国标ps流转rtmp直播流的实时转换方法. 背景技术: ps流全称是节目流(programstream),将一个节目的多个组成部分按照它们之间的互相关系进行组织并加 ...

  9. PS流包格式之PS/SYS/PSM/PES头

    PS流包格式 1,PS流的基本组成 PS流由很多个PS包组成. 每个PS包由如下组成: PS header + SYS header(I帧)+PSM header(I帧) +PES header+ P ...

最新文章

  1. 胖子哥的大数据之路(7)- 传统企业切入核心or外围
  2. pandas 补充知识:data_range函数
  3. 看漫画学python 豆瓣_各种表示“看”的词语
  4. *p++,(*p)++,*++p,++*p有什么不同
  5. FTP服务器的搭建与安全配置
  6. ENSP配置 实例九 动态Nat配置
  7. Java基础---认识正则表达式
  8. Crypto-Enigma密码机原理
  9. Java简易计算器制作
  10. 【C++ 程序】 TVJ Complex Calculator (v 2.1) 复数计算器
  11. OpenGL LookAt、Camera摄像机
  12. SD/eMMC初始化流程、读写流程(dwc mshc)
  13. 【SVAC】千目聚云:SVAC2.0已来 未来发展道路一片光明
  14. Tensorflow 从bin文件中读取数据并
  15. androbench测存储速率
  16. Xpath简介及用法整理
  17. FFmpeg将mp4格式视频转为flv
  18. SharePoint 2019 new Update
  19. 冯诺依曼结构、哈佛结构、改进型哈佛结构
  20. 71. 从Lotus Notes表单到XPage——兼谈程序里的二进制文件和文本文件

热门文章

  1. # 160_技巧_Power BI 新函数-计算工作日天数
  2. Android应用开发最强原创知识体系(更新至2019.11.21)
  3. 年轻人,月薪两万难吗?
  4. 初次搭建微服务报错java.Net.UnknownHostException
  5. 2022年互联网行业的寒流
  6. 非关系型数据库MongoDB学习笔记
  7. 使用sshj包报错“TransportException [HOST_KEY_NOT_VERIFIABLE] Could not verify `ssh-ed25519` host key”
  8. vue项目使用webpack打包后,图片不显示
  9. android控件的对齐方式,控件布局(顶部、底部)留着自用~~~
  10. 2021年中国妇科中成药市场竞争格局分析:CR5占市场份额近30%[图]