參考連接:
基於libRTMP的流媒體直播之 AAC、H264 推送:
http://billhoo.blog.51cto.com/2337751/1557646/
使用librtmp進行H264與AAC直播
http://www.codeman.net/2014/01/439.html
雷博CSDN博客
下面是我最近了解PUST H264流到RTMP服務器上的一些筆記,參考了上面的鏈接,看別人的不如自己動手實踐一下,以下便是實驗過程記錄。
一、FLV結構簡析
關於FLV的結構,雷博的博文已經詳細說明了,在這裏可以參考一張圖片
由上面的圖片可以看出,FLV的整體結構爲Header + Body. 其中Body則由 Tag + PreviousTagSizeN的方式組成,每一個Tag由Tag Header + Tag Data組成。
其中,在Video_File_Format_Specification_V10.pdf中,對每一個TAG的Tag Data有了更詳細的說明(暫時只關注視頻tag):
關於上面對Tag Data結構的說明,以前在做本地FLV視頻PUSH到RTMP的測試中,並沒有關注,只是直接將Tag Data封包進RTMP Packet裏面發送出去即可。
二、H264結構簡析
H264的碼流結構主要由SPS、 PPS、 IDR 幀(包含一個或多個 I-Slice)、 P 幀(包含一個或多個P-Slice)、 B 幀(包含一個或多個 B-Slice)等部分組成。
關於SPS和PPS,引用以下:
- 將一個視頻序列 (從 IDR 幀開始到下一個 IDR 幀之前的數據稱爲一個視頻序列) 全部圖像的共同特徵抽取出來,放在 SPS 語法單元中。
- 將各個圖像的典型特徵抽取出來,放在 PPS 語法單元中。
- 只有視頻序列之間才能切換 SPS,即只有 IDR 幀的第一個 slice 可以切換 SPS。
- 只有圖像之間才能切換 PPS,即只有每幀圖像的第一個 slice 才能切換 PPS
H.264 的所有語法結構最終都被封裝成 nalu。碼流中的 nalu單元必須定義合適的分隔符,否則無法區分,因此採用前綴碼“00 00 01”或者“00 00 00 01”來區分每一個nalu單元。在每個前綴碼後面緊跟的一個字節爲nalu的語法結構,由三部分組成forbidden_bit(1bit),nal_reference_bit(2bits)(優先級),nal_unit_type(5bits)(類型)。
forbidden_bit:禁止位。
nal_reference_bit:當前NAL的優先級,值越大,該NAL越重要。
nal_unit_type :NAL類型。
其中,我們着重需要關注的是nal_unit_type ,因爲它代表了這個NALU的具體荷載數據的類型,我們可以通過 nal_unit_type & 0x1f的方式來獲取其類型,具體定義如下:
三、H264流的封裝
在PUSH FLV到RTMP服務器的時候,我們只需將FLV 的Tag Data封裝進RTMP Packet,然後調用RTMP_SendPacket()函數將數據發送出去。
因此,在將H264 PUSH給RTMP服務器的時候,我們也需要按照FLV的格式將H264數據進行封包。因此我們需要構造視頻Tag,並且我們在發送第一包數據前,需要構造“AVC Sequence Header”,用於告訴RTMP服務器解碼相關的信息。
再次呈上上面的兩幅圖:
上面兩幅圖爲FLV 每一個TAG 中的Tag Data裏面的組成,因此我們需要根據上面的結構構造視頻同步包 和 H264碼流包
1)視頻同步包的構造[AVC Sequence Header]
視頻同步包的構造,其實是需要我們將H264中的SPS PPS按照一定的格式發給RTMP服務器,供RTMP服務器解碼器進行解碼。根據上面的圖片可知,當構造視頻同步包的時候,各個結構如下:
VIDEODATA
FrameType == 1
CodecID == 7
VideoData == AVCVIDEOPACKET
AVCVIDEOPACKET
AVCPacketType == 0x00
CompositionTime == 0x000000
Data == AVCDecoderConfigurationRecord
接下來繼續構造AVCDecoderConfigurationRecord,參考上面鏈接,AVCDecoderConfigurationRecord相關結構如下:
因此我們需要將H264中的SPS PPS信息按照上面的結構進行填充,代碼如下:
int send_sps_pps(unsigned char* index_body,int index_body_len)
{
unsigned char *sps,*pps;
int sps_temp,pps_temp;
int sps_len,pps_len;
int i,temp;
unsigned char body[1024];
memset(body,0,1024);
/* find *sps *pps */
temp = find_sps_pps(index_body,index_body_len,&sps_temp,&pps_temp,&sps_len,&pps_len);
if(temp < 0)
return -1;
sps = index_body + sps_temp;
pps = index_body + pps_temp;
/*去掉幀界定符*/
sps += 4;
pps += 4;
sps_len -= 4;
pps_len -= 4;
/*AVC head*/
i = 0;
body[i++] = 0x17;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
/*AVCDecoderConfigurationRecord*/
body[i++] = 0x01;
body[i++] = *(sps+1);
body[i++] = *(sps+2);
body[i++] = *(sps+3);
body[i++] = 0xff;
/*sps*/
body[i++] = 0xe1;
body[i++] = (sps_len >> 8) & 0xff;
body[i++] = sps_len & 0xff;
memcpy(&body[i],sps,sps_len);
i += sps_len;
/*pps*/
body[i++] = 0x01;
body[i++] = (pps_len >> 8) & 0xff;
body[i++] = (pps_len) & 0xff;
memcpy(&body[i],pps,pps_len);
i += pps_len;
/*send rtmp packet*/
if(rtmp_packet_send(body,i,RTMP_PACKET_TYPE_VIDEO,0) < 0)
return -2;
return temp;
}
2)H264普通數據包的構建
構建了上面的視頻同步包後,我們需要將H264的荷載的數據即H264的I P B等數據封包進去。根據上面的圖片,結構信息如下:
VIDEODATA
FrameType ==(I frame ? 1 :2)
CodecID == 7
VideoData == AVCVIDEOPACKET
AVCVIDEOPACKET
AVCPacketType == 0x01
CompositionTime == 0x000000
Data == NALU
上面的Data == H264 NALU Size + NALU Raw Data
代碼如下:
int send_rtmp_video(unsigned char* index_body,int index_body_len,unsigned int timestamp)
{
unsigned char *body;
int type;
/*去掉幀界定符*/
if (index_body[2] == 0x00) { /*00 00 00 01*/
index_body += 4;
index_body_len -= 4;
} else if (index_body[2] == 0x01){ /*00 00 01*/
index_body += 3;
index_body_len -= 3;
}
/*申請發送數據空間*/
body = (unsigned char*)malloc(index_body_len+9);
/*send video packet*/
memset(body,0,index_body_len+9);
/*key frame*/
body[0] = 0x27;
type = index_body[0]&0x1f;
if (type == NAL_SLICE_IDR)
body[0] = 0x17;
/*nal unit*/
body[1] = 0x01;
body[2] = 0x00;
body[3] = 0x00;
body[4] = 0x00;
//data length
body[5] = (index_body_len >> 24) & 0xff;
body[6] = (index_body_len >> 16) & 0xff;
body[7] = (index_body_len >> 8) & 0xff;
body[8] = (index_body_len ) & 0xff;
/*copy data*/
memcpy(&body[9],index_body,index_body_len);
/*send rtmp packet*/
if(rtmp_packet_send(body,index_body_len + 9,RTMP_PACKET_TYPE_VIDEO,timestamp) < 0)
return -1;
/*釋放發送數據空間*/
free(body);
return 0;
}
四、小結
在剛開始接觸H264 PUSH到RTMP時,一頭霧水,不理解如何進行操作,然後通過查閱資料,得到了初步的瞭解,將H264推流到RTMP服務器,關鍵的也就是視頻同步包的構造,和H264普通數據包的構造,以及在將這些數據封包進RTMP Packet時候,要注意時間戳之間的關係。除了這些,其他方面例如RTMP的初始化,建立連接等方面則和RTMPDUMP中的流程一致。