關於對H264碼流的TS的封裝的相關程式碼實現
在前段時間有分享一個H264封裝ps流到相關文章的,這次和大家分享下將H264封裝成TS流到相關實現,其實也是工作工作需要。依照上篇一樣,分段說明每個資料頭的封裝情況,當然,一樣也會加上rtp頭,方便以後的這方面到需求,如果開發不需要的話,可 以自行遮蔽掉,當然需要主要buffer指標的移動情況
2 封裝的各個頭到規則要點整個封裝過程也是和ps類似,但是最大到區別在於TS流到資料長度都是固定188大小來傳輸的,而PS流則是可變包結構,正因為兩者在結構上到差異,導致了它們在傳輸誤碼上的不同抵抗力.TS流由於採用了固定長度的資料包,當傳輸誤碼破壞了某一個TS包的同步資訊時,接收端可在固定的位置檢測到下一個TS包到同步資訊,從而恢復同步,避免資料丟失,PS流則長度可變到資料包,當某一個ps包同步資訊丟失時,接收端就無法進行資訊同步,也就無法確認下一步到同步資訊,導致了嚴重到資訊丟失。因此在環境惡劣的情況下, 傳輸碼丟失比較嚴重,一般都採用TS流來進行避免,當網路環境比較穩定,傳輸誤碼概率小,這個時候採用PS流進行傳送。
關於TS流需要了解下節目對映表(PAT:Program Associate Table)以及節目對映表(PMT:Program Map Table),當傳送到資料為視訊資料關鍵幀的時候,需要在包頭中新增PAT和PMT
具體結構體如下
封裝組成:(PAT +PMT) + TS + PES + H264 + (TS + H264 + TS + H264 ....)
資料長度:PES包的長度=188位元組-TS包頭長度(4位元組)-適應域長度(PES長度或者0)
注意:
a 每次資料定長包188個位元組,如果不足的則用1填充,這裡填充時值每一個bit位都填充,memset就是最好選擇b 因為我個人習慣,在封裝到時候當為關鍵幀的時候,我直接丟了PAM+PMT+TS+PES 然後填充滿188個位元組,這樣做提醒大家 是錯誤到,完全錯誤的,PES後必須跟H264資料。
c PES能表示的資料長度只有short, 兩個位元組,所以當資料長度超過的話,則需要考慮多個PES頭
3 各個部件到頭的虛擬碼實現
/* *@remark: 整體傳送資料的抽象邏輯處理函式介面 */ int rtsp_RTPPackage( RTP_SESSION_S *pRtpSender, int nFrameLen, StreamType_E enStreamType) { int nRet = 0; int bVideo = 1 ; int nSendDataOff = 0; int nSendSize = 0; int nPayLoadSize = 0; int nHasSend = 0; int IFrameFlag = 0; char TSFrameHdr[1024]; int nHead = 0; memset(TSFrameHdr, 0, 1024); memset(pRtpSender->stRtpPack, 0, RTP_MAX_PACKET_BUFF); bVideo = ((enStreamType == VIDEO_STREAM ) ? 1 : 0); // @remark: 判斷資料是否為I幀,如果為I幀的話,加上PAT和PMT if( (bVideo == 1) && pRtpSender->stAvData.u8IFrame == 1) { if((nRet = mk_ts_pat_packet(TSFrameHdr +nSendDataOff, pRtpSender->hHdlTs)) <= 0) { DBG_INFO(" mk_ts_pat_packet failed!\n"); return -1; } // @remark: 每次新增一個頭的時候,必須注意指標到偏移量 nSendDataOff += nRet; if((nRet = mk_ts_pmt_packet(TSFrameHdr + nSendDataOff, pRtpSender->hHdlTs)) <= 0) { DBG_INFO(" mk_ts_pmt_packet failed!\n"); return -1; } nSendDataOff += nRet; } // @remark: 新增PS頭,需要注意ps裡也有一個計數的欄位 if((nRet = mk_ts_packet(TSFrameHdr + nSendDataOff, pRtpSender->hHdlTs, 1, bVideo, pRtpSender->stAvData.u8IFrame, pRtpSender->stAvData.u64TimeStamp)) <= 0 ) { DBG_INFO(" mk_ts_packet failed!\n"); return -1; } nSendDataOff += nRet; //此欄位是用來計算ts長度,因為ts包是固定188位元組長度 nHead = nRet; // @remark: 新增PES頭,後面就必須接H264資料了,不能通過1來填充 if((nRet = mk_pes_packet(TSFrameHdr + nSendDataOff, bVideo, nFrameLen, 1, pRtpSender->stAvData.u64TimeStamp, pRtpSender->stAvData.u64TimeStamp)) <= 0 ) { DBG_INFO(" mk_pes_packet failed!\n"); return -1; } nSendDataOff += nRet; nHead += nRet; // @remark: 如果第一次傳送的資料長度大於剩餘長度,則先發送ts包剩餘長度的資料 if( nFrameLen > (TS_LOAD_LEN - nHead)) { memcpy(TSFrameHdr + nSendDataOff, pRtpSender->stAvData.data, TS_LOAD_LEN - nHead); nSendDataOff += (TS_LOAD_LEN - nHead); nHasSend = (TS_LOAD_LEN - nHead); if( rtsp_send_rtppack(TSFrameHdr, &nSendDataOff, pRtpSender->stAvData.u64TimeStamp, 0, (pRtpSender->stAvData.u8IFrame?1:0), bVideo, 1, pRtpSender) != 0 ) { DBG_INFO(" rtsp_send_pack failed!\n"); return -1; } } // @remark: 如果第一次傳送資料長度小於ts頭剩餘長度,則,傳送資料幀長度,剩餘沒有188長度的用1填充 else { memcpy(TSFrameHdr + nSendDataOff, pRtpSender->stAvData.data, nFrameLen); nSendDataOff += nFrameLen; nHasSend = nFrameLen; memset(TSFrameHdr +nSendDataOff, 0xFF, (TS_LOAD_LEN-nHead - nFrameLen)); nSendDataOff += (TS_LOAD_LEN -nHead- nFrameLen); if( rtsp_send_rtppack(TSFrameHdr, &nSendDataOff, pRtpSender->stAvData.u64TimeStamp, 1, (pRtpSender->stAvData.u8IFrame?1:0), bVideo, 1, pRtpSender) != 0 ) { DBG_INFO(" rtsp_send_rtppack failed!\n"); return -1; } } // 對應的資料便宜長度,因為我處理的時候時固定1460到rtp包傳送資料,所以這裡會處理偏移,方便新增rtp頭 nPayLoadSize = RTP_MAX_PACKET_BUFF - 4 - RTP_HDR_LEN - (4+6) * 7; // 減去rtp頭,ts頭 ,一個rtp包最多7個ts包 nFrameLen -= (TS_LOAD_LEN - nHead); // @remark: 第二次傳送資料了,此時傳送資料時候,就需要外再新增ps頭了 while(nFrameLen > 0 ) { nSendSize = (nFrameLen > nPayLoadSize) ? nPayLoadSize : nFrameLen; if( rtsp_send_rtppack(pRtpSender->stAvData.data + nHasSend, &nSendSize, pRtpSender->stAvData.u64TimeStamp, ((nSendSize == nFrameLen) ? 1 : 0), IFrameFlag, bVideo, 0, pRtpSender) != 0 ) { DBG_INFO(" rtsp_send_rtppack failed!\n"); return -1; } nFrameLen -= nSendSize; nHasSend += nSendSize; memset(pRtpSender->stRtpPack, 0, RTP_MAX_PACKET_BUFF); IFrameFlag = 0; } return 0; }
/*
*@remark : 新增pat頭
*/
int mk_ts_pat_packet(char *buf, int handle)
{
int nOffset = 0;
int nRet = 0;
if (!buf)
{
return 0;
}
if (0 >= (nRet = ts_header(buf, handle, TS_TYPE_PAT, 1)))
{
return 0;
}
nOffset += nRet;
if (0 >= (nRet = ts_pointer_field(buf + nOffset)))
{
return 0;
}
nOffset += nRet;
if (0 >= (nRet = ts_pat_header(buf + nOffset)))
{
return 0;
}
nOffset += nRet;
// 每一個pat都會當成一個ts包來處理,所以每次剩餘部分用1來充填完
memset(buf + nOffset, 0xFF, TS_PACKET_SIZE - nOffset);
return TS_PACKET_SIZE;
}
int ts_pat_header(char *buf)
{
BITS_BUFFER_S bits;
if (!buf)
{
return 0;
}
bits_initwrite(&bits, 32, (unsigned char *)buf);
bits_write(&bits, 8, 0x00); // table id, 固定為0x00
bits_write(&bits, 1, 1); // section syntax indicator, 固定為1
bits_write(&bits, 1, 0); // zero, 0
bits_write(&bits, 2, 0x03); // reserved1, 固定為0x03
bits_write(&bits, 12, 0x0D); // section length, 表示這個位元組後面有用的位元組數, 包括CRC32
bits_write(&bits, 16, 0x0001); // transport stream id, 用來區別其他的TS流
bits_write(&bits, 2, 0x03); // reserved2, 固定為0x03
bits_write(&bits, 5, 0x00); // version number, 範圍0-31
bits_write(&bits, 1, 1); // current next indicator, 0 下一個表有效, 1當前傳送的PAT表可以使用
bits_write(&bits, 8, 0x00); // section number, PAT可能分為多段傳輸,第一段為00
bits_write(&bits, 8, 0x00); // last section number
bits_write(&bits, 16, 0x0001); // program number
bits_write(&bits, 3, 0x07); // reserved3和pmt_pid是一組,共有幾個頻道由program number指示
bits_write(&bits, 13, TS_PID_PMT); // pmt of pid in ts head
bits_write(&bits, 8, 0x9F); // CRC_32 先暫時寫死
bits_write(&bits, 8, 0xC7);
bits_write(&bits, 8, 0x62);
bits_write(&bits, 8, 0x58);
bits_align(&bits);
return bits.i_data;
}
/*
*@remaark: 新增PMT頭
*/
int mk_ts_pmt_packet(char *buf, int handle)
{
int nOffset = 0;
int nRet = 0;
if (!buf)
{
return 0;
}
if (0 >= (nRet = ts_header(buf, handle, TS_TYPE_PMT, 1)))
{
return 0;
}
nOffset += nRet;
if (0 >= (nRet = ts_pointer_field(buf + nOffset)))
{
return 0;
}
nOffset += nRet;
if (0 >= (nRet = ts_pmt_header(buf + nOffset)))
{
return 0;
}
nOffset += nRet;
// 每一個pmt都會當成一個ts包來處理,所以每次剩餘部分用1來充填完
memset(buf + nOffset, 0xFF, TS_PACKET_SIZE - nOffset);
return TS_PACKET_SIZE;
}
int ts_pmt_header(char *buf)
{
BITS_BUFFER_S bits;
if (!buf)
{
return 0;
}
bits_initwrite(&bits, 32, (unsigned char *)buf);
bits_write(&bits, 8, 0x02); // table id, 固定為0x02
bits_write(&bits, 1, 1); // section syntax indicator, 固定為1
bits_write(&bits, 1, 0); // zero, 0
bits_write(&bits, 2, 0x03); // reserved1, 固定為0x03
bits_write(&bits, 12, 0x1C); // section length, 表示這個位元組後面有用的位元組數, 包括CRC32
bits_write(&bits, 16, 0x0001); // program number, 表示當前的PMT關聯到的頻道號碼
bits_write(&bits, 2, 0x03); // reserved2, 固定為0x03
bits_write(&bits, 5, 0x00); // version number, 範圍0-31
bits_write(&bits, 1, 1); // current next indicator, 0 下一個表有效, 1當前傳送的PAT表可以使用
bits_write(&bits, 8, 0x00); // section number, PAT可能分為多段傳輸,第一段為00
bits_write(&bits, 8, 0x00); // last section number
bits_write(&bits, 3, 0x07); // reserved3, 固定為0x07
bits_write(&bits, 13, TS_PID_VIDEO); // pcr of pid in ts head, 如果對於私有資料流的節目定義與PCR無關,這個域的值將為0x1FFF
bits_write(&bits, 4, 0x0F); // reserved4, 固定為0x0F
bits_write(&bits, 12, 0x00); // program info length, 前兩位bit為00
bits_write(&bits, 8, TS_PMT_STREAMTYPE_H264_VIDEO); // stream type, 標誌是Video還是Audio還是其他資料
bits_write(&bits, 3, 0x07); // reserved, 固定為0x07
bits_write(&bits, 13, TS_PID_VIDEO); // elementary of pid in ts head
bits_write(&bits, 4, 0x0F); // reserved, 固定為0x0F
bits_write(&bits, 12, 0x00); // elementary stream info length, 前兩位bit為00
bits_write(&bits, 8, TS_PMT_STREAMTYPE_11172_AUDIO); // stream type, 標誌是Video還是Audio還是其他資料
bits_write(&bits, 3, 0x07); // reserved, 固定為0x07
bits_write(&bits, 13, TS_PID_AUDIO); // elementary of pid in ts head
bits_write(&bits, 4, 0x0F); // reserved, 固定為0x0F
bits_write(&bits, 12, 0x00); // elementary stream info length, 前兩位bit為00
bits_write(&bits, 8, 0xA4); // stream type, 標誌是Video還是Audio還是其他資料
bits_write(&bits, 3, 0x07); // reserved, 固定為0x07
bits_write(&bits, 13, 0x00A4); // elementary of pid in ts head
bits_write(&bits, 4, 0x0F); // reserved, 固定為0x0F
bits_write(&bits, 12, 0x00); // elementary stream info length, 前兩位bit為00
bits_write(&bits, 8, 0x34); //CRC_32 先暫時寫死
bits_write(&bits, 8, 0x12);
bits_write(&bits, 8, 0xA3);
bits_write(&bits, 8, 0x72);
bits_align(&bits);
return bits.i_data;
}
/*
*@remark: ts頭的封裝
*/
int mk_ts_packet(char *buf, int handle, int bStart, int bVideo, int bIFrame, unsigned long long timestamp)
{
int nOffset = 0;
int nRet = 0;
if (!buf)
{
return 0;
}
if (0 >= (nRet = ts_header(buf, handle, bVideo ? TS_TYPE_VIDEO : TS_TYPE_AUDIO, bStart)))
{
return 0;
}
nOffset += nRet;
if (0 >= (nRet = ts_adaptation_field(buf + nOffset, bStart, bVideo && (bIFrame), timestamp)))
{
return 0;
}
nOffset += nRet;
return nOffset;
}
/* *@remark: ts頭相關封裝
* PSI 包括了PAT、PMT、NIT、CAT
* PSI--Program Specific Information, PAT--program association table, PMT--program map table
* NIT--network information table, CAT--Conditional Access Table
* 一個網路中可以有多個TS流(用PAT中的ts_id區分)
* 一個TS流中可以有多個頻道(用PAT中的pnumber、pmt_pid區分)
* 一個頻道中可以有多個PES流(用PMT中的mpt_stream區分)
*/
int ts_header(char *buf, int handle, TS_TYPE_E type, int bStart)
{
BITS_BUFFER_S bits;
TS_MNG_S *pMng = (TS_MNG_S *)handle;
if (!buf || !handle || TS_TYPE_BEGIN >= type || TS_TYPE_END <= type)
{
return 0;
}
bits_initwrite(&bits, 32, (unsigned char *)buf);
bits_write(&bits, 8, 0x47); // sync_byte, 固定為0x47,表示後面的是一個TS分組
// payload unit start indicator根據TS packet究竟是包含PES packet還是包含PSI data而設定不同值
// 1. 若包含的是PES packet header, 設為1, 如果是PES packet餘下內容, 則設為0
// 2. 若包含的是PSI data, 設為1, 則payload的第一個byte將是point_field, 0則表示payload中沒有point_field
// 3. 若此TS packet為null packet, 此flag設為0
bits_write(&bits, 1, 0); // transport error indicator
bits_write(&bits, 1, bStart); // payload unit start indicator
bits_write(&bits, 1, 0); // transport priority, 1表示高優先順序
if (TS_TYPE_PAT == type)
{
bits_write(&bits, 13, 0x00); // pid, 0x00 PAT, 0x01 CAT
}
else if (TS_TYPE_PMT == type)
{
bits_write(&bits, 13, TS_PID_PMT);
}
else if (TS_TYPE_VIDEO == type)
{
bits_write(&bits, 13, TS_PID_VIDEO);
}
else if (TS_TYPE_AUDIO == type)
{
bits_write(&bits, 13, TS_PID_AUDIO);
}
bits_write(&bits, 2, 0); // transport scrambling control, 傳輸加擾控制
if (TS_TYPE_PAT == type || TS_TYPE_PMT == type)
{
// continuity counter, 是具有同一PID值的TS包之間的連續計數值
// 當分組的adaption_field_control欄位為00話10時,該欄位不遞增
bits_write(&bits, 2, 0x01); // adaptation field control, 00 forbid, 01 have payload, 10 have adaptation, 11 have payload and adaptation
bits_write(&bits, 4, pMng->nPatCounter); // continuity counter, 0~15
if (TS_TYPE_PAT != type)
{
pMng->nPatCounter++;
pMng->nPatCounter &= 0x0F;
}
}
else
{
bits_write(&bits, 2, 0x03); // 第一位表示有無調整欄位,第二位表示有無有效負載
bits_write(&bits, 4, pMng->nContinuityCounter);
pMng->nContinuityCounter++;
pMng->nContinuityCounter &= 0x0F;
}
bits_align(&bits);
return bits.i_data;
}
/*
*remark:新增pes頭
*/
int mk_pes_packet(char *buf, int bVideo, int length, int bDtsEn, unsigned long long pts, unsigned long long dts)
{
PES_HEAD_S pesHead;
PES_OPTION_S pesOption;
PES_PTS_S pesPts;
PES_PTS_S pesDts;
if (!buf)
{
return 0;
}
if( bVideo == 1)
{
// 視訊的取樣頻率為90kHZ,則增量為3600
pts = pts * 9 / 100; // 90000Hz
dts = dts * 9 / 100; // 90000Hz
}
else
{
// 音訊的話,則需要按照8000HZ來計算增量[需要的話]
pts = pts * 8 / 1000; // 8000Hz
dts = dts * 8 / 1000; // 8000Hz
}
memset(&pesHead, 0, sizeof(pesHead));
memset(&pesOption, 0, sizeof(pesOption));
memset(&pesPts, 0, sizeof(pesPts));
memset(&pesDts, 0, sizeof(pesDts));
pesHead.startcode = htonl(0x000001) >> 8;
pesHead.stream_id = bVideo ? 0xE0 : 0xC0;
if (PES_MAX_SIZE < length)
{
pesHead.pack_len = 0;
}
else
{
pesHead.pack_len = htons(length + sizeof(pesOption) + sizeof(pesPts) + (bDtsEn ? sizeof(pesDts) : 0));
}
pesOption.fixed = 0x02;
pesOption.pts_dts = bDtsEn ? 0x03 : 0x02;
pesOption.head_len = sizeof(pesPts) + (bDtsEn ? sizeof(pesDts) : 0);
pesPts.fixed2 = pesPts.fixed3 = pesPts.fixed4 = 0x01;
pesPts.fixed1 = bDtsEn ? 0x03 : 0x02;
pesPts.ts1 = (pts >> 30) & 0x07;
pesPts.ts2 = (pts >> 22) & 0xFF;
pesPts.ts3 = (pts >> 15) & 0x7F;
pesPts.ts4 = (pts >> 7) & 0xFF;
pesPts.ts5 = pts & 0x7F;
pesDts.fixed1 = pesDts.fixed2 = pesDts.fixed3 = pesDts.fixed4 = 0x01;
pesDts.ts1 = (dts >> 30) & 0x07;
pesDts.ts2 = (dts >> 22) & 0xFF;
pesDts.ts3 = (dts >> 15) & 0x7F;
pesDts.ts4 = (dts >> 7) & 0xFF;
pesDts.ts5 = dts & 0x7F;
char *head = buf;
memcpy(head, &pesHead, sizeof(pesHead));
head += sizeof(pesHead);
memcpy(head, &pesOption, sizeof(pesOption));
head += sizeof(pesOption);
memcpy(head, &pesPts, sizeof(pesPts));
head += sizeof(pesPts);
if (bDtsEn)
{
memcpy(head, &pesDts, sizeof(pesDts));
head += sizeof(pesPts);
}
return (head - buf);
}
/*
*@remark: 最後封裝rtp頭併發送最終封裝好到完整的資料包
*/
int rtsp_send_rtppack(char *Databuf, int *datalen, unsigned long curtimestamp, int mark_flag, int IFrameFlag, int bVideo, int nFrameStart, RTP_SESSION_S *pRtpSender)
{
int nHasSend = 0;
int nRet = 0;
int nTsHeadNum = 0;
int nHadDataLen = 0;
int nTcpSendLen = 0;
static unsigned short cSeqnum;
// @remark:表示為資料的第一次傳送,所以不需要額外再新增ts頭
if( nFrameStart == 1 )
{
nRet = mk_rtp_packet(pRtpSender->stRtpPack + nHasSend, mark_flag, IFrameFlag, bVideo, ++cSeqnum, (curtimestamp * 9/100));
nHasSend += nRet;
memcpy(pRtpSender->stRtpPack + nHasSend, Databuf, *datalen);
nHasSend += *datalen;
}
else // 不是第一次傳送此幀資料的話,則需要新增封裝新的ts包,並新增ts頭
{
// rtp+ rtp_ext + ts +data
nRet = mk_rtp_packet(pRtpSender->stRtpPack + nHasSend, mark_flag, IFrameFlag, bVideo, ++cSeqnum, (curtimestamp * 9/100));
nHasSend += nRet;
while(*datalen > 0 && nTsHeadNum < 7)
{
nRet = mk_ts_packet(pRtpSender->stRtpPack + nHasSend , pRtpSender->hHdlTs, 0, bVideo, (IFrameFlag > 0 ? 1:0), curtimestamp);
nHasSend += nRet;
if(*datalen < (TS_LOAD_LEN- nRet))
{
memcpy(pRtpSender->stRtpPack + nHasSend, Databuf + nHadDataLen, *datalen);
nHasSend += *datalen;
nHadDataLen += *datalen;
//不夠Ts188用1補充
memset(pRtpSender->stRtpPack + nHasSend, 0xFF, TS_LOAD_LEN- nRet - (*datalen));
nHasSend += (TS_LOAD_LEN - nRet - *datalen);
}
else
{
memcpy(pRtpSender->stRtpPack + nHasSend, Databuf + nHadDataLen, TS_LOAD_LEN - nRet);
nHasSend += (TS_LOAD_LEN - nRet);
*datalen -= (TS_LOAD_LEN - nRet);
nHadDataLen += (TS_LOAD_LEN - nRet);
}
nTsHeadNum ++;
}
*datalen = nHadDataLen; //實際傳送裸資料到長度
}
if(pRtpSender->RtspsockFd <= 0 )
{
DBG_INFO("send rtp packet socket error\n");
return -1;
}
nTcpSendLen = hi_tcp_noblock_send(pRtpSender->RtspsockFd, pRtpSender->stRtpPack, nHasSend, NULL,1500);
if(nTcpSendLen != nHasSend )
{
DBG_INFO("send rtp packet failed:%s\n",strerror(errno));
return -1;
}
return 0;
}
/*
*remark: 上面用到的一些巨集定義和一些關於位元組操作的函式,很多一些開源到視訊處理的庫都能看到,
為了方便也都將貼出來分享,當然也可以參考下vlc裡面的原始碼
*/
/*@remark: 常量定義 */
#define TS_PID_PMT (0x62)
#define TS_PID_VIDEO (0x65)
#define TS_PID_AUDIO (0x84)
#define TS_PMT_STREAMTYPE_11172_AUDIO (0x03)
#define TS_PMT_STREAMTYPE_13818_AUDIO (0x04)
#define TS_PMT_STREAMTYPE_AAC_AUDIO (0x0F)
#define TS_PMT_STREAMTYPE_H264_VIDEO (0x1B)
/* @remark: 結構體定義 */
typedef struct
{
int i_size; // p_data位元組數
int i_data; // 當前操作位元組的位置
unsigned char i_mask; // 當前操作位的掩碼
unsigned char *p_data; // bits buffer
} BITS_BUFFER_S;
typedef struct
{
unsigned int startcode : 24; // 固定為00 00 01
unsigned int stream_id : 8; // 0xC0-0xDF audio stream, 0xE0-0xEF video stream, 0xBD Private stream 1, 0xBE Padding stream, 0xBF Private stream 2
unsigned short pack_len; // PES packet length
} __attribute__ ((packed)) PES_HEAD_S;
typedef struct
{
#if (BYTE_ORDER == LITTLE_ENDIAN)
unsigned char original : 1; // original or copy, 原版或拷貝
unsigned char copyright : 1; // copyright flag
unsigned char align : 1; // data alignment indicator, 資料定位指示符
unsigned char priority : 1; // PES priority
unsigned char scramb : 2; // PES Scrambling control, 加擾控制
unsigned char fixed : 2; // 固定為10
unsigned char exten : 1; // PES extension flag
unsigned char crc : 1; // PES CRC flag
unsigned char acopy : 1; // additional copy info flag
unsigned char trick : 1; // DSM(Digital Storage Media) trick mode flag
unsigned char rate : 1; // ES rate flag, ES流速率標誌
unsigned char escr : 1; // ESCR(Elementary Stream Clock Reference) flag, ES流時鐘基準標誌
unsigned char pts_dts : 2; // PTS DTS flags, 00 no PTS and DTS, 01 forbid, 10 have PTS, 11 have PTS and DTS
#elif (BYTE_ORDER == BIG_ENDIAN)
unsigned char fixed : 2; // 固定為10
unsigned char scramb : 2; // PES Scrambling control, 加擾控制
unsigned char priority : 1; // PES priority
unsigned char align : 1; // data alignment indicator, 資料定位指示符
unsigned char copyright : 1; // copyright flag
unsigned char original : 1; // original or copy, 原版或拷貝
unsigned char pts_dts : 2; // PTS DTS flags, 00 no PTS and DTS, 01 forbid, 10 have PTS, 11 have PTS and DTS
unsigned char escr : 1; // ESCR(Elementary Stream Clock Reference) flag, ES流時鐘基準標誌
unsigned char rate : 1; // ES rate flag, ES流速率標誌
unsigned char trick : 1; // DSM(Digital Storage Media) trick mode flag
unsigned char acopy : 1; // additional copy info flag
unsigned char crc : 1; // PES CRC flag
unsigned char exten : 1; // PES extension flag
#endif
unsigned char head_len; // PES header data length
} __attribute__ ((packed)) PES_OPTION_S;
typedef struct
{// ts total 33 bits
#if (BYTE_ORDER == LITTLE_ENDIAN)
unsigned char fixed2 : 1; // 固定為1
unsigned char ts1 : 3; // bit30-32
unsigned char fixed1 : 4; // DTS為0x01, PTS為0x02, PTS+DTS則PTS為0x03
unsigned char ts2; // bit22-29
unsigned char fixed3 : 1; // 固定為1
unsigned char ts3 : 7; // bit15-21
unsigned char ts4; // bit7-14
unsigned char fixed4 : 1; // 固定為1
unsigned char ts5 : 7; // bit0-6
#elif (BYTE_ORDER == BIG_ENDIAN)
unsigned char fixed1 : 4; // DTS為0x01, PTS為0x02, PTS+DTS則PTS為0x03
unsigned char ts1 : 3; // bit30-32
unsigned char fixed2 : 1; // 固定為1
unsigned char ts2; // bit22-29
unsigned char ts3 : 7; // bit15-21
unsigned char fixed3 : 1; // 固定為1
unsigned char ts4; // bit7-14
unsigned char ts5 : 7; // bit0-6
unsigned char fixed4 : 1; // 固定為1
#endif
} __attribute__ ((packed)) PES_PTS_S;
/* remark:介面函式定義 */
int bits_initwrite(BITS_BUFFER_S *p_buffer, int i_size, unsigned char *p_data)
{
if (!p_data)
{
return -1;
}
p_buffer->i_size = i_size;
p_buffer->i_data = 0;
p_buffer->i_mask = 0x80;
p_buffer->p_data = p_data;
p_buffer->p_data[0] = 0;
return 0;
}
void bits_align(BITS_BUFFER_S *p_buffer)
{
if (p_buffer->i_mask != 0x80 && p_buffer->i_data < p_buffer->i_size)
{
p_buffer->i_mask = 0x80;
p_buffer->i_data++;
p_buffer->p_data[p_buffer->i_data] = 0x00;
}
}
inline void bits_write(BITS_BUFFER_S *p_buffer, int i_count, unsigned long i_bits)
{
while (i_count > 0)
{
i_count--;
if ((i_bits >> i_count ) & 0x01)
{
p_buffer->p_data[p_buffer->i_data] |= p_buffer->i_mask;
}
else
{
p_buffer->p_data[p_buffer->i_data] &= ~p_buffer->i_mask;
}
p_buffer->i_mask >>= 1;
if (p_buffer->i_mask == 0)
{
p_buffer->i_data++;
p_buffer->i_mask = 0x80;
}
}
}
int bits_initread(BITS_BUFFER_S *p_buffer, int i_size, unsigned char *p_data)
{
if (!p_data)
{
return -1;
}
p_buffer->i_size = i_size;
p_buffer->i_data = 0;
p_buffer->i_mask = 0x80;
p_buffer->p_data = p_data;
return 0;
}
inline int bits_read(BITS_BUFFER_S *p_buffer, int i_count, unsigned long *i_bits)
{
if (!i_bits)
{
return -1;
}
*i_bits = 0;
while (i_count > 0)
{
i_count--;
if (p_buffer->p_data[p_buffer->i_data] & p_buffer->i_mask)
{
*i_bits |= 0x01;
}
if (i_count)
{
*i_bits = *i_bits << 1;
}
p_buffer->i_mask >>= 1;
if(p_buffer->i_mask == 0)
{
p_buffer->i_data++;
p_buffer->i_mask = 0x80;
}
}
return 0;
}
5 寫在最後
看過我上一篇的關於ps封裝的可能會注意的,關於壓位元組的處理,兩篇博文到處理方式有些差異。關於我這個我簡單說兩點
第一次是這個ts的處理裡面封裝是另外一個同事實現的,我因為用到所以拿來使用,但是上次呼叫封裝都是自己完成。第二個就是
ps和ts的處理方式不一樣。一個定長,一個不定長。所以這樣處理,也挺好的,我也有點懶,所以沒有改過來。
相關推薦
關於對H264碼流的TS的封裝的相關程式碼實現
1 寫在開始之前 在前段時間有分享一個H264封裝ps流到相關文章的,這次和大家分享下將H264封裝成TS流到相關實現,其實也是工作工作需要。依照上篇一樣,分段說明每個資料頭的封裝情況,當然,一樣也會加上rtp頭,方便以後的這方面到需求,如果開發不
關於對H264碼流的TS的封裝的相關代碼實現
有效 當前 完成 read ble tco and mark comm 轉自:http://www.cnblogs.com/lidabo/p/6604998.html 1 寫在開始之前 在前段時間有分享一個H264封裝ps流到相關文章的,這次和
關於對H264碼流的PS的封裝的相關程式碼實現
1、寫在開始之前: 最近因為新工作要維護別人留下的GB模組程式碼,先熟悉了流程,然後也試著封裝了下ps流,結果也能通過測試正常預覽了,當然,其中開發讀文件的頭疼,預覽花屏,卡幀的事情都有遇到,當時慢慢的看文件,整理邏輯,也就都順利解決了,下
關於對H264碼流的PS的封裝的相關代碼實現
真心 clip gef 但是 占用 udp 大致 結果 方法 轉自:http://www.cnblogs.com/lidabo/p/6604988.html 1、寫在開始之前: 最近因為新工作要維護別人留下的GB模塊代碼,先熟悉了流程,然後也試著封裝
Java利用JNI呼叫FFMpeg對h264碼流進行解碼
前期配置工作: 使用JNI呼叫: java端: package com.recon.action; public class Decode { public native String loadfile(String s); //
實現對rtp H264碼流的組幀
rtp打包h264,包含了三種類型的包: 一個rtp包攜帶了一幀資料(single) 多個rtp包攜帶了一幀資料(FU-A) 一個rtp包攜帶了多幀資料(STAP-A) 在實際應用中絕大部分採用的是前兩種方式,對方式1常見的是對nalu的sps,pps進行打包
FFMPEG 實時解碼網路H264碼流,RTP封裝
初學FFMPEG和H264,解碼視訊流時遇到了很多麻煩,記錄一下研究成果。 我使用的FFMPEG 2.5.2版本,使用av_parser_parse2重組影象幀時遇到了一下麻煩! 下面是主要程式碼: RTP頭定義, typedef struct { /**/
H264碼流中SPS PPS詳解<轉>
擴展 vlc 地址 逗號 部分 級別 軟件 第一個 bottom 轉載地址:https://zhuanlan.zhihu.com/p/27896239 1 SPS和PPS從何處而來? 2 SPS和PPS中的每個參數起什麽作用? 3 如何解析SDP中
FFmpeg In Android - H264碼流解碼/OpenGL ES渲染
主要思路是FFmpeg解碼H264得到一張yuv420p圖片後,傳遞給opengl es在著色器內部做圖片轉換yuv->rgb,然後通過紋理貼圖的方式渲染出來.這種方式的效率更高.核心程式碼如下: #include "common.h" #include "gl_util.h"
FFmpeg In Android - H264碼流解碼/儲存Yuv
本節例子原始碼_NativeH264Android,修改自ffmpeg原始碼目錄/doc/examples/decode_video.c H264的碼流結構 H.264原始碼流(又稱為“裸流”)是由一個一個的NALU組成的,包括I幀,B幀,P幀等等,他們的結構如下圖所示: 其中每個
H264碼流中SPS PPS詳解
轉載地址:https://zhuanlan.zhihu.com/p/27896239 1 SPS和PPS從何處而來? 2 SPS和PPS中的每個引數起什麼作用? 3 如何解析SDP中包含的H.264的SPS和PPS串? 1 客戶端抓包 在做客戶端視訊解碼時,一
gstreamer將H264碼流轉為avi視訊檔案示例
是時候記錄怎麼使用gstreamer庫將h264碼流轉為avi、mp4、flv等視訊檔案了。 下圖是本片示例視訊合成的流程圖,其中H264 採集與佇列實現部分程式碼未貼上。 總體思想是,“視訊合成主執行緒”根據視訊資料通道建立gstreamer視訊合成pipe
將H264碼流打包成RTP包
H264碼流打包成RTP包的程式碼如下:#include <stdio.h> #include <stdlib.h> #include <conio.h> #include <string.h> #incl
h264碼流rtp打包(一)
一幀image編碼完的資料儲存在h264buffer中,編碼後的h264碼流的大小為nH264Size 因為對於NALU,並不是一幀對應一個NALU,而是對於SLICE而言,一個slice就封裝層一個nal,所以一幀可以有多個slice,即一幀有多個nal。
H264編碼器11( H.264 探索 第二部分 H264碼流格式)
來自:https://segmentfault.com/a/1190000006698552 表1中描述了所有可能的資料包型別。 Type Definition 0 Undefined
H264編碼器8( H264碼流打包分析(精華))
來自:https://www.cnblogs.com/lidabo/p/4602422.html H264碼流打包分析 SODB 資料位元串-->最原始的編碼資料 RBSP 原始位元組序列載荷-->在SODB的後面填加了結尾位元(RBSP trailing bits 一個bit“1”)若
Wireshark提取RTP包中的H264碼流
1-- Dump RTP h.264 payload to raw h.264 file (*.264) 2-- According to RFC3984 to dissector H264 payload of RTP to NALU, and write it 3--
RTP協議全解析(H264碼流和PS流)
寫在前面:RTP的解析,網上找了很多資料,但是都不全,所以我力圖整理出一個比較全面的解析, 其中借鑑了很多文章,我都列在了文章最後,在此表示感謝。 網際網路的發展離不開大家的無私奉獻,我決定從我做起,希望大家支援。 1、RTP Header解析
RTP協議解析和H264碼流提取
一、 h264基礎概念SODB: 資料位元串-->最原始的編碼資料RBSP: 原始位元組序列載荷-->在SODB的後面填加了結尾位元(RBSP trailing bits 一個bit“1”)若干位元“0”,以便位元組對齊。EBSP: 擴充套件位元組序列載荷– >在RBS
從wireshark中獲取H264碼流詳解
1、首先從https://github.com/volvet/h264extractor打包下載 2、讀README.md 全文如下: # h264extractor wireshark plugin to extract h264 stream from rt