1. 程式人生 > >H264和音訊流打包成TS流 (MPEG2-TS)

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

 

技術在於交流、溝通,轉載請註明出處並保持作品的完整性。

原文: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