H264和音訊流打包成TS流 (MPEG2-TS)
技術在於交流、溝通,轉載請註明出處並保持作品的完整性。
原文:https://blog.csdn.net/hiwubihe/article/details/80865920
[本系列相關文章]
- H264和音訊流打包成PS流 (MPEG2-PS)
- PS流解複用成H264和音訊流(ES提取)
- H264和音訊流打包成TS流 (MPEG2-TS)
- TS流解複用成H264和音訊流(ES提取)
- FLV檔案格式基礎
- 解複用FLV檔案(基於FFMPEG解析FLV(h264+aac))
- 解複用FLV檔案(不用FFMPEG,C++實現)
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