H264和音频流打包成PS流 (MPEG2 您所在的位置:网站首页 ts封装音视频 H264和音频流打包成PS流 (MPEG2

H264和音频流打包成PS流 (MPEG2

2024-07-01 01:32| 来源: 网络整理| 查看: 265

 

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

原文: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封装中的基本元素

ES 音视频采集编码后等待打包PS的单元,H264就是一个I帧,P帧或者其他H264辅助信息。音频的打包方式都是一样的,目前PsMuxer.dll只支持H264和各种音频的ES流,本来打算添加H265和MPEG4-PART2的,考虑到H265并不是iso13818-1规定的类型,MPEG4-PART2打包和H264基本相同,H265只是标准扩展的,所以厂商实现不统一,就去掉这部分内容,后期需要实现可以添加。标准支持情况在Table 2-29中有表述PES 编码层之上的传输或者存储单元,一个PS包里面可以有多个PES包,这些PES可以是一路流的多个单元(如I帧太大超过标准的65535,只能分包),也可以是多路流的多个单元,如PS包中包含一个PES视频包和一个PES音频包。解复用PS流主要只有PES包里面有ES。PES头包括类别码,和PTS,DTS信息。PS包头 表示一个PS包的开始。一般来说一个节目流PS只包括一个节目流,一个节目流中有多路流,如视频流1,视频流2,音频流,解复用PS流主要依据每一路流stream_id区别。解复用把不同stream_id的流分别做不同处理,如在不同屏幕显示等。PS头部主要包括比特率,注意是以50B为单位的,PsMuxer.dll中处理方式为统计一段时间数据流量,然后填写,过一段时间重新统计下一段时间区间的平均比特率。PS头部还包括时钟参考信息,为当前PS段最后1B数据期望送入解码器的时间,和PES中的PTS不一样,但是PsMuxer.dll中简单处理,把视频流中PTS放入PS头部时钟参考,时间以90KHZ为单位。PS系统头,系统头 主要包括 头长度,视频流个数,音频流个数,及每一路流信息列举(stream_id,视频还是音频),stream_id怎么分配在下面讲解。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前一般都会包含SPS、PPS等NALU,因此将SPS、PPS、IDR 的NALU封装为一个PS包,包括ps头,然后加上PS system header,PS system map,PES 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 #include #ifdef PSMUXER_EXPORTS #define DLLEXPORT __declspec(dllexport) //#define DLLEXPORT #else #define DLLEXPORT #endif #else #define DLLEXPORT #define WINAPI #endif //WIN32 #include /// #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 ; //帧PTS LONG64 lPts ; //帧DTS LONG64 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 为合法句柄,>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 = 16) && (type 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为单位,所以3600 g_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) { 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

 

 

 

 

 

 

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有