由於要在crtmpserver中實現Http Live Streaming ,本人花了接近3個星期的時間,研究將H264與AAC打包為TS流並能在Ipad上通過HTML5播放,由於沒有任何現成代碼可供參考,打包代碼全部手寫,打包格式主要參考ISO/ICE 18318-1.pdf。期間碰到很多問題,走了不少彎路,符合標准的TS不一定能在Ipad上播放,但是Ipad上播放的TS一定是符合標准的,可以說是TS標准中的特例。現將遇到的主要問題以及要點總結如下:
【打包流程以及相關圖:】
【視頻H264到TS注意要點:】
<1>. 如果h264的包大於 65535 的話,可以設置PES_packet_length為0 ,具體參見ISO/ICE 13818-1.pdf 49 / 174 中關於PES_packet_length的描述
打包PES, 直接讀取一幀h264的內容, 此時我們設置PES_packet_length的值為0000
表示不指定PES包的長度,ISO/ICE 13818-1.pdf 49 / 174 有說明,這主要是方便
當一幀H264的長度大於PES_packet_length(2個字節)能表示的最大長度65535
的時候分包的問題, 這里我們設置PES_packet_length的長度為0000之后 , 那么即使該H264視頻幀的長度
大於65535個字節也不需要分多個PES包存放, 事實證明這樣做是可以的, ipad可播放
-----------------
<2>. PES頭中的stream_id 好像是無所謂,隨便改了幾個都可以。 不過找到的幾個可以ipad播放的樣本文件中都設置的是0xe0
而在蘋果的http://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/HTTP_Live_Streaming_Metadata_Spec/2/2.html
里面的2.4 PES Stream Format 提供的表里面,使用的是0xbd 表示的是private_stream_id_1,不知道這個說明是否專門針對ID3,但是建議設置為0xeo,我看到的幾個TS樣本文件都是這么設置的。
-----------------
<3>. 對於視頻來說PTS和DTS都需要,可以設置為一樣的,如果只有PTS,沒有DTS是不行的。可以將pts的值賦值給dts,pts_dts_flag的值應該設置為0x03,也就是二進制的'11'
-----------------
<4>. pmt中的 stream_type = 0x1b 表示 H264 , stream_type = 0x0f 表示AAC
-----------------
<5>. ipad不需要pcr都可以播放,可以將pmt頭中的 PCR_PID設置為0x1fff 表示沒有PCR ,參考ISO/ICE 13818-1.pdf 65 / 174, 在之后的所有關於adaptation_field中設置PCR_flag: 00 就可以了,並且在adaptation_field中也不需要寫入pcr部分的值
-----------------
<6>. 在每一幀的視頻幀被打包到pes的時候,其開頭一定要加上 00 00 00 01 09 xx 這個nal。否則就有問題
蘋果官網有如下說明:
https://developer.apple.com/library/ios/#documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
10.These settings are the current recommendations. There are also certain requirements.
The current mediastreamsegmenter tool works only with MPEG-2 Transport Streams as defined in ISO/IEC 13818.
The transport stream must contain H.264 (MPEG-4, part 10) video and AAC or MPEG audio.
If AAC audio is used, it must have ADTS headers. H.264 video access units must use Access Unit Delimiter NALs,
and must be in unique PES packets.
其中 H.264 video access units must use Access Unit Delimiter NALs 中的 Access Unit Delimiter NAL 在H264的文檔中是09 ,但是有什么用,我也不太清楚。根據我的實驗09后面的一個字節好像設置成什么都可以,但是不能設置為00
-----------------
<7>. 關於pts的計算方法
// 1s = 90000 time scale , 一幀就應該是 90000/video_frame_rate 個timescale
static uint32_t video_frame_rate = 30;
static uint32_t video_pts_increment = 90000 / video_frame_rate; //用一秒鍾除以幀率,得到每一幀應該耗時是多少,單位是 timescale單位
static uint64_t video_pts = 0;
在以后的計算中,每生成一幀的ts,該幀的pts值應該是 video_pts += video_pts_increment; 偽代碼如下:
while(..)
{
ReadH264(h264_buffer)
PacketTS(h264_buffer,out_ts_buffer,video_pts);
video_pts += video_pts_increment;
}
<8>. 音頻應該是只需要pts,不需要dts。
音頻pts的計算方法同上,只不過不是通過幀率,而是通過采樣率。
uint32_t audio_pts_increment = (90000 * audio_samples_per_frame) / audio_sample_rate;
上面的偽代碼就不貼了。
<9>. 關於pat,pmt的插入時間,實際上pat,pmt是要連續插入到ts流中的,並不是開始插入之后就完了。因為ts要保證從任何時候都可以開始播放。目前我是每4幀視頻(這4幀H264可能被打包成很多的ts包,而不止4包)插入一次pat,pmt包。
<10>. PES音視頻數據包的ts頭中的continuity_counter 必須是連續的,從小到大的,當增長到15之后再從0開始循環。第一包可以不從0開始。但是之后的continuity_counter 必須是連續的,並且是按照步長1增長的。continuity_counter的增長相對於所有PID相同的包他們的增長是獨立的,例如第一個PAT包其continuity_counter值是0,那么第二個PAT包的continuity_counter是1。 第一個PMT包其continuity_counter值是2,第二個PMT包其continuity_counter值是3。
【音頻AAC到TS注意要點:】
aac不需要設置DTS,只要PTS就可以了,加上DTS可能還播放不了
aac打包成PES的時候,要想在ipad上播放必須設置PES_packet_length的長度,而視頻可以設置為0,但是音頻必須設置為正確的長度值,aac的長度不可能超過65535,所以也不可能導致PES_packet_length溢出。
否則ipad播放不了。但是QQ影音可以播放。這也許是ipad的特性。
//data_len:AAC音頻裸流的長度
// 3:PES頭
//5: 如果是音頻的話會有5個字節的PTS
_ts_packet_pes_header.PES_packet_length_16bit = data_len + 3 + 5 ;
【關於CRC32校驗算法:】
static uint32_t crc32table[256] = {
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,
0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,
0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,
0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4,
0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,
0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,
0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,
0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,
0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,
0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,
0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,
0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,
0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};
uint32_t CRC32(const uint8_t *data, int len)
{
int i;
uint32_t crc = 0xFFFFFFFF;
for(i = 0; i < len; i++)
crc = (crc << 8) ^ crc32table[((crc >> 24) ^ *data++) & 0xFF];
return crc;
}
【關於TS流中的填充字節】
TS流中有2中不同的填充形式,因為TS每一包要求是188個字節,當不足188個字節的時候, 必須要補充到188個字節,這就涉及到填充的問題。
<1>. 如果TS包中承載的是PSI數據(PAT,PMT等),那么其填充是在該包的最后一個有效字節的后面填充0xFF直到滿足188個字節為止。
解碼器會丟棄這些字節,具體說明參考 ISO_IEC 13818-1.pdf 60 / 174
<2>. 如果TS包中承載的是PES數據,那么當不足188個字節的時候,需要使用adaptation_field 這個域,也就是指定TS包中的adaptation_field_control的值來控制payload與adaptation_field的承載關系,同時指定PES中的adaptation_field_length指定填充多少字節,具體的填充字節值應該是隨意的建議使用FF,並且此adaptation_field的使用好像只能在PES中使用。 ISO_IEC 13818-1.pdf 39 / 174