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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hiwubihe/article/details/80865920

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

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

[本系列相关文章]

MPEG2-TS主要应用于数字电视,苹果的流媒体协议HLS(Http Live Streaming Apple公司定义的用于实时流传输的协议,HLS基于HTTP协议实现)其中媒体流传输格式为MPEG2-TS。PS流和TS流的概念是在MPEG2的ISO/IEC-13818标准的第一部分“系统”中提出的。其提出的目的是提供MPEG2编码比特的存储与传输方案。本文将基于C/C++提供一个TS流的打包库TsMuxer.dll,并提供DEMO测试程序。打包程序包括一个TsMuxerDemo和TsMuxer库,文档包括主要参考的几篇文章"iso13818-1.pdf" "PS流和TS流介绍.docx","视音频数据PS封装-offset.doc"。TS与PS的区别在于TS每个包长度固定为188,PS每个包长度不确定,TS适合信道不稳定的情况下的传输。部分代码参考ffmpeg中处理ts的源文件mpegtsenc.c,不理解可以结合次代码理解。 

TS流总体机构图

TS流组成单元

1.ES ES为音视频层的负载单元,可以是视频一帧,该概念与PS是相同的,可以参照“H264和音频流打包成PS流 (MPEG2-PS)”文章介绍。

2.PES PES为视频编码层之上的处理单元,该概念与PS是相同的,可以参照“H264和音频流打包成PS流 (MPEG2-PS)”文章介绍。不同的是PS流中一个PS包可以包含多个PES,一般有分包界限大小,超过解析就需要分包,分包界限存储为2B,所以最大分包界限65535。但是在TS中,一个PES包不限制大小,当大小超过65535时,设置长度字段为0。然后把65535按照188进行切割,组成多个TS包传输。PES层类型区别一般是stream_id;

3.TS TS层固定大小188B,TS层类型区别为PID。

4.PAT TS把PES和PSI(节目特殊信息)封装成188字节为单位的TS流,这里的PSI包括常见的PAT和PMT。TS流需要不定期的在视频帧中间发送PAT信息,因为有可能TS流在播放过程中加入新的节目,TsMuxerDemo.exe中采取没当发送I帧时,发送PAT和PMT。整个PS流中只有一个PAT,描述了当前TS流有多少个节目,每个节目的PMT的PID时多少。PAT需要单独封装一个TS包。

        

// PAT
typedef struct
{
	unsigned char table_id;
	unsigned char section_length_high4: 4,
				  reserved1: 2,
				  zero: 1,
				  section_syntax_indicator: 1;
	unsigned char section_length_low8;
	unsigned char transport_stream_id_high8;
	unsigned char transport_stream_id_low8;
	unsigned char current_next_indicator: 1,
			      version_number: 5,
				  reserved2: 2;
	unsigned char section_number;
	unsigned char last_section_number;
}pat_section;

typedef struct
{
	unsigned char program_number_high8;
	unsigned char program_number_low8;
	unsigned char program_map_PID_high5: 5,
				  reserved: 3;
	unsigned char program_map_PID_low8;
}pat_map_array;

5.PMT TS流中,一个节目对应一个PMT表,有多少路节目就有多少个PMT表,PMT表中包含了当前节目所包含的音视频流信息,包括音视频流的PID信息。所以区别音视频流信息,可以从PES的stream_id区别,也可以从TS头的PID区别。PMT需要单独封装一个TS包。

TsMuxer.dll封装流程

部分参考代码:

bool CTsMuxerContext::FramePackage(int iProgram,int iStream, PmFrameInfo stFrameInfo,unsigned char * outBuf, int* pOutSize)
{
	unsigned char *pBufPtr = (unsigned char *)outBuf;
	int	iOutSize = * pOutSize;



	if (!stFrameInfo.pFrame || !pBufPtr )
	{
		return false;
	}
	int iPatPmtLen = 0;
	//加入PAT和PMT
	if (stFrameInfo.bPatPmt)
	{
		iPatPmtLen = 188 + 188 * stProgramInfo.program_num;
		if (iOutSize < iPatPmtLen)
		{
			return false;
		}
		//添加PAT数据包
		genTsPatPacket(pBufPtr);
		pBufPtr += 188;
		int iIndex;
		for (iIndex = 0; iIndex < stProgramInfo.program_num; iIndex++)
		{
			//加入PMT数据包
			genTsPmtPacket(pBufPtr, iIndex);
			pBufPtr += 188;
		}
		stProgramInfo.patPmtCounter++;
	}

	// 生成实体流的TS包
	int iRetLen = genTsPesPacket(iProgram,iStream, stFrameInfo,pBufPtr, iOutSize-iPatPmtLen);
	
	if (iRetLen <= 0)
	{
		return false;
	}

	*pOutSize = (iPatPmtLen + iRetLen);
	return true;
}
//生成实体流数据包
int CTsMuxerContext::genTsPesPacket(int iProgram,int iStream, PmFrameInfo stFrameInfo,unsigned char * outBuf, int iOutSize)
{

	int iTotalLength  = 0         ;
	unsigned char *pOutBuffer = outBuf	  ;
	int iOutBufferLen = iOutSize ;
	//PES头
	unsigned char szPesHeader[TS_PES_HEAD_LEN];
	
	
	//获取PES流的PID(该路流TS层标志)
	unsigned short sPid = stProgramInfo.prog[iProgram].stream[iStream].es_pid;
	////获取PES流的stream_id(该路流PES层标志)
	unsigned char cStreamId = stProgramInfo.prog[iProgram].stream[iStream].stream_id;

	//PES长度
	int iPesLen = genTsPesHeader(cStreamId, stFrameInfo.lPts, stFrameInfo.iFrameLen, szPesHeader, -1);

	if (iPesLen < 0)
	{
		return -1;
	}
	iPesLen += stFrameInfo.iFrameLen;


	int start_pos;

	// PES是否需要分割 第一个包需要时钟参考PCR(6B) 所以第一个包负载最大长度 188 -6 -6=176
	if (iPesLen <= 176/*188-12*/)
	{
		iTotalLength = 188;
		if (iOutBufferLen < iTotalLength)
		{
			return -1;
		}

		start_pos = genTsPacketHeader(pOutBuffer, sPid, stProgramInfo.prog[iProgram].stream[iStream].continuity_counter++, iPesLen);
		//赋值PCR
		setTsHeaderWithPcr(pOutBuffer, stFrameInfo.lPts);
		//赋值PES头
		memcpy(pOutBuffer + start_pos, szPesHeader, 19);
		//赋值负载
		memcpy(pOutBuffer + start_pos + 19, stFrameInfo.pFrame, stFrameInfo.iFrameLen);
	}
	//需要分包处理的情况
	else
	{
		//帧信息游标
		unsigned char *pFrameCur = stFrameInfo.pFrame;
		//PES尾部长 除去第一个包
		int iPesTailLen = iPesLen - 176;
		//分包个数 除去第一个包
		int iPackNum = (iPesTailLen + 181) / 182; // 188 - 6
		//返回包总长度
		iTotalLength = 188 * (iPackNum + 1);

		if (iOutBufferLen < iTotalLength)
		{
			return -1;
		}

		// 先放第一包, 因为带PCR, 所以TS头部长12B

		start_pos = genTsPacketHeader(pOutBuffer, sPid, stProgramInfo.prog[iProgram].stream[iStream].continuity_counter++, 176);
		//赋值PCR
		setTsHeaderWithPcr(pOutBuffer, stFrameInfo.lPts);
		//赋值PES头
		memcpy(pOutBuffer + start_pos, szPesHeader, 19);
		//赋值负载
		memcpy(pOutBuffer + start_pos + 19, pFrameCur, 176 - 19);


		//游标移动
		pFrameCur  += (176 - 19);
		pOutBuffer += 188;
		

		int iIndex;
		// 放入后续包, 后续包不含PCR, 所以TS头部只有6B
		for (iIndex = 0; iIndex < iPackNum; iIndex++)
		{
			int payload_len = 182;
			//最后一个包负载长度
			if (iIndex == iPackNum - 1)
			{
				payload_len = iPesTailLen - iIndex * 182;
			}

			start_pos = genTsPacketHeader(pOutBuffer, sPid, stProgramInfo.prog[iProgram].stream[iStream].continuity_counter++, payload_len);
			//取消赋值PCR
			setTsHeaderWithOutPcr(pOutBuffer);
			//赋值负载
			memcpy(pOutBuffer + start_pos , pFrameCur, payload_len);
			pFrameCur	+= payload_len;
			pOutBuffer  += 188;
		}
	}


	return iTotalLength;

}

TS流复用库TsMuxer.dll头文件

/*******************************************************************************
Copyright (c) wubihe Tech. Co., Ltd. All rights reserved.
--------------------------------------------------------------------------------

Date Created:	2014-10-25
Author:			wubihe
Description:	TS流封装库头文件

--------------------------------------------------------------------------------
Modification History
DATE          AUTHOR          DESCRIPTION
--------------------------------------------------------------------------------

********************************************************************************/
#ifndef ITSMUXER_H_
#define ITSMUXER_H_

#ifdef WIN32
#include <windows.h>
#include <windef.h>
#ifdef TSMUXER_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

/******************************************************************************
TsMuxer.dll宏定义
*******************************************************************************/





/******************************************************************************
TsMuxer.dll错误码定义,TsMuxer.dll库错误码的范围:0-255
*******************************************************************************/



/******************************************************************************
TsMuxer.dll数据结构定义
*******************************************************************************/
///日志级别类型
typedef enum _TM_LOG_LEVEL
{
	TM_LOG_TRACE =		0,
	TM_LOG_DEBUG =		1,                      
	TM_LOG_INFO  =		2,                      
	TM_LOG_WARN  =		3,                      
	TM_LOG_ERROR =		4, 
	TM_LOG_FATAL =		5
} TM_LOG_LEVEL;

//复合流类型
typedef enum _TM_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
} TM_STREAM_TYPE;



//帧信息
typedef struct tagPmFrameInfo
{
	//帧数据
	unsigned char * pFrame		;
	//帧大小
	int				iFrameLen	;
	//帧PTS
	LONG64			lPts		; 
	//帧DTS
	LONG64			lDts        ;   
	//是否添加PAT/PMT
	bool		    bPatPmt		;
} PmFrameInfo;



/****************************************************************************
                        General Callback
                          通用回调接口
****************************************************************************/
typedef void(CALLBACK *TM_LogCBFun)(TM_LOG_LEVEL nLogLevel, const char *szMessage, void* pUserData );  


/****************************************************************************
                        General System Interface
                            通用系统接口
****************************************************************************/

/**************************************************************************
* Function Name  : TM_SetLogCallBack
* Description    : 设置库日志回调
* Parameters     : pLogFunc	(日志回调函数)	
* Parameters     : pUserData(日志回调用户数据)
* Return Type    : void
* Last Modified  : wubihe
***************************************************************************/

DLLEXPORT void  WINAPI TM_SetLogCallBack(TM_LogCBFun pLogFunc,	void* pUserData);


/**************************************************************************
* Function Name  : TM_CreateMuxHandle
* Description    : 创建PS流复用器句柄
* Parameters     : 
* Return Type    : int >1为合法句柄,<=0非法 最大支持299路
* Last Modified  : wubihe
***************************************************************************/
DLLEXPORT int  WINAPI  TM_CreateMuxHandle();


/**************************************************************************
* Function Name  : TM_AddProgram
* Description    : TS流复用器中添加节目
* Parameters     : iHandle	  复用器句柄
* Return Type    : int 节目句柄
* Last Modified  : wubihe
***************************************************************************/
DLLEXPORT int  WINAPI  TM_AddProgram(int iHandle);

/**************************************************************************
* Function Name  : TM_AddStream
* Description    : TS流复用器中添加节目流
* Parameters     : iHandle	  复用器句柄
* Parameters     : iProgram	  节目句柄
* Parameters     : eType      流类型
* Return Type    : int 流句柄 >=0 为合法句柄,<0非法 
* Last Modified  : wubihe
***************************************************************************/

DLLEXPORT int  WINAPI  TM_AddStream(int iHandle,int iProgram,TM_STREAM_TYPE eType,bool bSetKeyStream);

/**************************************************************************
* Function Name  : TM_FrameTrans
* Description    : TS流复用器打包TS
* Parameters     : iHandle		复用器句柄
* Parameters     : iProgram		节目句柄
* Parameters     : iStream		流句柄
* Parameters     : stFrameInfo	帧信息
* Parameters     : outBuf		输出缓存
* Parameters     : pOutSize     输入为输出缓存大小,输出为实际封装后数据大小
* Return Type    : bool 
* Last Modified  : wubihe
***************************************************************************/
DLLEXPORT bool  WINAPI TM_FrameTrans(int iHandle, int iProgram,int iStream, PmFrameInfo stFrameInfo,unsigned char * outBuf, int* pOutSize);

/**************************************************************************
* Function Name  : TM_DestroyMuxHandle
* Description    : TS流复用器销毁
* Parameters     : iHandle		复用器句柄
* Return Type    : void 
* Last Modified  : wubihe
***************************************************************************/
DLLEXPORT void WINAPI  TM_DestroyMuxHandle(int iHandle);
#ifdef __cplusplus
}
#endif



#endif /* IPSMUXER_H_ */

TS流复用库TsMuxer.dll调用流程

TS流复用库TsMuxer.dll调用demo代码

/*******************************************************************************
Copyright (c) wubihe Tech. Co., Ltd. All rights reserved.
--------------------------------------------------------------------------------

Date Created:	2014-10-25
Author:			wubihe QQ:1269122125 Email:[email protected]
Description:	TS流封装库TsMuxer使用Demo,Demo完成h264流的TS封装,试用版本Demo
				只能运行240S 需要授权库或者全部源码请发送E-Mail联系作者
--------------------------------------------------------------------------------
Modification History
DATE          AUTHOR          DESCRIPTION
--------------------------------------------------------------------------------

********************************************************************************/
#include "excpt.h"
#include "TIOBuffer.h"
#include "ITsMuxer.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 iProgram ,int iStream,unsigned char* buf, int len)
{
	PmFrameInfo stFrameInfo;

	stFrameInfo.pFrame		= buf;
	stFrameInfo.iFrameLen	= len;
	stFrameInfo.lPts		= g_Pts;
	stFrameInfo.lDts		= g_Dts;

	unsigned char c = 0;
	int iHeadLen;
	if (!isH264Or265Frame(buf, &c,&iHeadLen))
	{
		return;
	}

	NAL_type Type = getH264NALtype(c);
	if((Type ==  NAL_IDR)||(Type ==  NAL_SPS)||(Type ==  NAL_PPS)||(Type ==  NAL_SEI))
	{
		stFrameInfo.bPatPmt = true;
	}
	else
	{
		stFrameInfo.bPatPmt = false;
	}
	


	int MuxOutSize = 0;

	int iOutSize = MAX_OUT_BUFFER_SIZE;
	bool bResult = TM_FrameTrans(iHandle,iProgram, iStream, stFrameInfo,g_pMuxBuf, &iOutSize);

	if (bResult && iOutSize > 0)
	{
		fwrite(g_pMuxBuf, iOutSize, 1, gOutputFile);
		fflush(gOutputFile);
	}
	else
	{
		printf("mux error !\n");
	}


	//I帧PTS递增
	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.ts", 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 = TM_CreateMuxHandle();
	if(iMuxHandle <= 0)
	{
		printf("TM_CreateMuxHandle Error!\n");
		return 0;
	}

	//复用器中添加一个节目
	int iProgramId = TM_AddProgram(iMuxHandle);
	if(iProgramId < 0)
	{
		printf("TM_AddProgram Error!\n");
		return 0;
	}

	//复用器中指定节目流中添加H264媒体流 设定该路视频流作为时钟参考
	int iStreamId = TM_AddStream(iMuxHandle,iProgramId,MUXSER_VIDEO_TYPE_H264,true);
	if(iStreamId < 0)
	{
		printf("TM_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 ,iProgramId,iStreamId,firstPacket.data, firstPacket.length);
			
			gH264ParserBuff.skip(secondPacket.length);
			
		}

        //实际播放应该按照比特率播放

		iReadSize = fread(gszReadBuffer, 1, MAX_BUFFER_SIZE, gInputFile);
	}

	fclose(gInputFile);
	fclose(gOutputFile);
	printf("Ts file: %s Generate Success!\n",szOutFileName);
	printf("GetChar To Exit!\n");
	getchar();
	return 0;
}

TS流复用库TsMuxer.dll调用demo使用方法

windows控制台在bin目录下运行命令 TsMuxerDemo.exe huangdun.264 ,目录下生成TS文件huangdun.264.ts

Elecard Stream Analysis 分析码流

分析图1  总体结构                                      

分析图2 PAT分析

分析图3 PMT分析

分析图4 I帧分布

编译环境:   Win7_64bit+VS2008

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

猜你喜欢

转载自blog.csdn.net/hiwubihe/article/details/80865920