使用librtmp進行H264與AAC直播


http://my.oschina.net/jerikc/blog/501948#OSC_h3_13

使用librtmp進行H264與AAC直播

發表於5個月前(2015-09-06 23:19)   閱讀( 1276) | 評論(0) 11人收藏此文章, 我要收藏
0

libx264 版本是 128
libfaac 版本是 1.28

1、幀的划分

1.1 H.264 幀

對於 H.264 而言每幀的界定符為 00 00 00 01 或者 00 00 01。

比如下面的 h264 文件片斷這就包含三幀數據:

00 00 00 01 67 42 C0 28 DA 01 E0 08 9F 96 10 00
00 03 00 10 00 00 03 01 48 F1 83 2A 00 00 00 01
68 CE 3C 80 00 00 01 06 05 FF FF 5D DC 45 E9 BD
E6 D9 48 B7 96 2C D8 20 D9 23 EE EF …

第一幀是 00 00 00 01 67 42 C0 28 DA 01 E0 08 9F 96 10 00 00 03 00 10 00 00 03 01 48 F1 83 2A
第二幀是 00 00 00 01 68 CE 3C 80
第三幀是 00 00 01 06 05 FF FF 5D DC 45 E9 BD E6 D9 48 B7 96 2C D8 20 D9 23 EE EF ..

幀類型有:
NAL_SLICE = 1
NAL_SLICE_DPA = 2
NAL_SLICE_DPB = 3
NAL_SLICE_DPC = 4
NAL_SLICE_IDR = 5
NAL_SEI = 6
NAL_SPS = 7
NAL_PPS = 8
NAL_AUD = 9
NAL_FILLER = 12,

我們發送 RTMP 數據時只需要知道四種幀類型,其它類型我都把它規類成非關鍵幀。分別是
NAL_SPS(7), sps 幀
NAL_PPS(8), pps 幀
NAL_SLICE_IDR(5), 關鍵幀
NAL_SLICE(1) 非關鍵幀

幀類型的方式判斷為界面符后首字節的低四位。
第一幀的幀類型為: 0x67 & 0x1F = 7,這是一個 SPS 幀
第二幀的幀類型為: 0x68 & 0x1F = 8,這是一個 PPS 幀
第三幀的幀類型為: 0x06 & 0x1F = 6,這是一個 SEI 幀

以上是我們利用幀界定符划分幀,並可以判斷每一個幀的類型。

注意:如果是壓縮圖像成 H264 幀,我們就可不必進行幀界定,因為每一次壓縮的輸出都明確了該幀的大小(包括界定符),每一次的壓縮的結果可能包函多幀。一會具體討論。

1.2 AAC 幀

對於 AAC 幀它的界定符是 FF F1

這里我就不舉例了,可通過查看 AAC 的二進制文件可以看到如下的幀結構:
FF F1 50 80 24 9F FD DE 04 00 00 6C 69 62 66 61 61 63 20 31 2E 32 38 00 00 42 15 95 ..

注意:那么對於 AAC 而言加上界定符每一幀的前 7 字節是幀的描述信息,也就是說 AAC 的祼數據是除去前面的 7 個字節的,在發送 RTMP 時,我們要去掉這 7 個字節。同樣,如果我們是一邊壓縮一邊發送 RTMP,我們同樣不需要界定幀,因為 libfaac 每次壓縮完成的輸出就是一個完整的幀數據,我們只需要將該幀打包發送即可。

綜合上面的所述,如果我們只是一邊壓縮一邊將壓縮結果發送到 RTMP 服務器,那我們就可以不用對幀進行界定,如果我們是發送 H264 與 AAC 文件,那我們就要對幀進行界定。

2.視頻與音頻的編碼信息

如果我們只是簡答的將壓縮數據打包發送給 RTMP 服務器,那么 RTMP 服務器是不可以對數據進行解碼和播放的,在這之前我們要將音視頻的視頻的編碼信息發送給 RTMP 服務器。很多人可能苦於尋找下面的三個編碼參數而不得要領。其實要想得到也是很簡單的。

2.1 (H264)SPS

對於 H264 而言,SPS 就是編碼后的第一幀。如果是讀取 H264 文件,就是第一個幀界定符與第二幀界定符中間的數據長度是 4。

2.2 (H264)PPS

對於 H264 而言,PPS 就是編碼后的第二幀。如果是讀取 H264 文件,就是第二個幀界定符與第三幀界定符中間的數據,長度不固定。

2.3 (AAC)AudioDecoderSpecificInfo

這個長度為 2 個字節,可以通過計算或者調用函數獲取。建議通過調用faacEncGetDecoderSpecificInfo(fh,&spec,&len); 獲取。
一般情況雙聲道 44100 采樣下,該值是 0x1210

3.librtmp 的使用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*分配與初始化*/
rtmp = RTMP_Alloc();
RTMP_Init(rtmp);
 
/*設置URL*/
if (RTMP_SetupURL(rtmp,rtmp_url) == FALSE) {
     log (LOG_ERR, "RTMP_SetupURL() failed!" );
     RTMP_Free(rtmp);
     return -1;
}
 
/*設置可寫,即發布流,這個函數必須在連接前使用,否則無效*/
RTMP_EnableWrite(rtmp);
 
/*連接服務器*/
if (RTMP_Connect(rtmp, NULL) == FALSE) {
     log (LOG_ERR, "RTMP_Connect() failed!" );
     RTMP_Free(rtmp);
     return -1;
}
 
/*連接流*/
if (RTMP_ConnectStream(rtmp,0) == FALSE) {
     log (LOG_ERR, "RTMP_ConnectStream() failed!" );
     RTMP_Close(rtmp);
     RTMP_Free(rtmp);
     return -1;
}

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*定義包頭長度,RTMP_MAX_HEADER_SIZE為rtmp.h中定義值為18*/
 
#define RTMP_HEAD_SIZE   (sizeof(RTMPPacket)+RTMP_MAX_HEADER_SIZE)
 
RTMPPacket * packet;
unsigned char * body;
 
/*分配包內存和初始化,len為包體長度*/
packet = (RTMPPacket *) malloc (RTMP_HEAD_SIZE+len);
memset (packet,0,RTMP_HEAD_SIZE);
 
/*包體內存*/
packet->m_body = ( char *)packet + RTMP_HEAD_SIZE;
body = (unsigned char *)packet->m_body;
packet->m_nBodySize = len;
 
/*
  * 此處省略包體填充
  */
packet->m_hasAbsTimestamp = 0;
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; /*此處為類型有兩種一種是音頻,一種是視頻*/
packet->m_nInfoField2 = rtmp->m_stream_id;
packet->m_nChannel = 0x04;
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
packet->m_nTimeStamp = timeoffset;
 
/*發送*/
if (RTMP_IsConnected(rtmp)) {
     ret = RTMP_SendPacket(rtmp,packet,TRUE); /*TRUE為放進發送隊列,FALSE是不放進發送隊列,直接發送*/
}
 
/*釋放內存*/
free (packet);

 

?
1
2
3
/*關閉與釋放*/
RTMP_Close(rtmp);
RTMP_Free(rtmp);

4.包類型

4.1 H.264編碼信息幀

H.264 的編碼信息幀是發送給 RTMP 服務器稱為 AVC sequence header,RTMP 服務器只有收到 AVC sequence header 中的 sps, pps 才能解析后續發送的 H264 幀。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
int send_video_sps_pps()
{
     RTMPPacket * packet;
     unsigned char * body;
     int i;
 
     packet = (RTMPPacket *) malloc (RTMP_HEAD_SIZE+1024);
     memset (packet,0,RTMP_HEAD_SIZE);
 
     packet->m_body = ( char *)packet + RTMP_HEAD_SIZE;
     body = (unsigned char *)packet->m_body;
 
     memcpy (winsys->pps,buf,len);
     winsys->pps_len = len;
 
     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;
 
     packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
     packet->m_nBodySize = i;
     packet->m_nChannel = 0x04;
     packet->m_nTimeStamp = 0;
     packet->m_hasAbsTimestamp = 0;
     packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
     packet->m_nInfoField2 = rtmp->m_stream_id;
 
     /*調用發送接口*/
     RTMP_SendPacket(rtmp,packet,TRUE);
     free (packet);   
 
     return 0;
}

sps 與 pps 怎么獲取到呢?

在前面已經說過,H264 的第 1 幀是 sps 幀, pps 是第 2 幀。

我們在編碼時會調用如下接口

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
size = x264_encoder_encode(cx->hd,&nal,&n,pic,&pout);
 
int i,last;
for (i = 0,last = 0;i < n;i++) {
     if (nal[i].i_type == NAL_SPS) {
         sps_len = nal[i].i_payload-4;
         memcpy (sps,nal[i].p_payload+4,sps_len);
     } else if (nal[i].i_type == NAL_PPS) {
         pps_len = nal[i].i_payload-4;
         memcpy (pps,nal[i].p_payload+4,pps_len);
 
         /*發送sps pps*/
         send_video_sps_pps();   
 
     } else {
 
         /*發送普通幀*/
         send_rtmp_video(nal[i].p_payload,nal[i].i_payload);
     }
     last += nal[i].i_payload;
}

我完全可以不用知道 sps, pps 的具體意義:)

4.2 H.264關鍵幀

 

4.3 H.264非關鍵幀

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
int send_rtmp_video(unsigned char * buf, int len)
{
     int type;
     long timeoffset;
     RTMPPacket * packet;
     unsigned char * body;
 
     timeoffset = GetTickCount() - start_time;  /*start_time為開始直播時的時間戳*/
 
     /*去掉幀界定符*/
     if (buf[2] == 0x00) { /*00 00 00 01*/
             buf += 4;
             len -= 4;
     } else if (buf[2] == 0x01){ /*00 00 01*/
             buf += 3;
             len -= 3;
     }
     type = buf[0]&0x1f;
 
     packet = (RTMPPacket *)base_malloc(RTMP_HEAD_SIZE+len+9);
     memset (packet,0,RTMP_HEAD_SIZE);
 
     packet->m_body = ( char *)packet + RTMP_HEAD_SIZE;
     packet->m_nBodySize = len + 9;
 
     /*send video packet*/
     body = (unsigned char *)packet->m_body;
     memset (body,0,len+9);
 
     /*key frame*/
     body[0] = 0x27;
     if (type == NAL_SLICE_IDR) {
             body[0] = 0x17;
     }
 
     body[1] = 0x01;   /*nal unit*/
     body[2] = 0x00;
     body[3] = 0x00;
     body[4] = 0x00;
 
     body[5] = (len >> 24) & 0xff;
     body[6] = (len >> 16) & 0xff;
     body[7] = (len >>  8) & 0xff;
     body[8] = (len ) & 0xff;
 
     /*copy data*/
     memcpy (&body[9],buf,len);
 
     packet->m_hasAbsTimestamp = 0;
     packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
     packet->m_nInfoField2 = winsys->rtmp->m_stream_id;
     packet->m_nChannel = 0x04;
     packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
     packet->m_nTimeStamp = timeoffset;
 
     /*調用發送接口*/
     RTMP_SendPacket(rtmp,packet,TRUE);
     free (packet);
}

這里要說明一下:
比如說 x264_encoder_encode 輸出了 6 幀。
分別是 sps 幀, pps 幀,關鍵幀,非關鍵幀,非關鍵幀,非關鍵幀。
發送結果應該是, sps,pps 合成為一幀調用發送函數,剩下 4 幀,除去每個 nal 的界定符,分別發送每一個 nal。

在 libx264 中每一次調用 x264_encoder_encode 輸出了 n 個幀,我們要從這 n 個幀里找出 sps 和 pps,剩下的分次全部發送 nal,sps 與 pps 的幀界定符都是 00 00 00 01,而普通幀可能是 00 00 00 01 也有可能 00 00 01。

如果 x264_encoder_encode 里沒有 sps 幀與 pps 幀,則結果除去第一幀的界定符所以幀做為一個整體調用發送函數,它們的類型是由第一幀類型決定。

另外,H264 的流的第 1 幀一定是 sps 幀(包含幀界定符為 8 個字節),第 2 幀一定是 pps幀。

4.4 AAC編碼信息

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int cap_rtmp_sendaac_spec(unsigned char *spec_buf, int spec_len)
{
     RTMPPacket * packet;
     unsigned char * body;
     int len;
 
     len = spec_len;  /*spec data長度,一般是2*/
 
     packet = (RTMPPacket *)base_malloc(RTMP_HEAD_SIZE+len+2);
     memset (packet,0,RTMP_HEAD_SIZE);
 
     packet->m_body = ( char *)packet + RTMP_HEAD_SIZE;
     body = (unsigned char *)packet->m_body;
 
     /*AF 00 + AAC RAW data*/
     body[0] = 0xAF;
     body[1] = 0x00;
     memcpy (&body[2],spec_buf,len); /*spec_buf是AAC sequence header數據*/
 
     packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
     packet->m_nBodySize = len+2;
     packet->m_nChannel = 0x04;
     packet->m_nTimeStamp = 0;
     packet->m_hasAbsTimestamp = 0;
     packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
     packet->m_nInfoField2 = rtmp->m_stream_id;
 
     /*調用發送接口*/
     RTMP_SendPacket(rtmp,packet,TRUE);
 
     return TRUE;
}

對於音頻解碼參數 AAC sequence header 是通過

下面是獲得 AAC sequence header 的方法

 

?
1
2
3
4
5
6
7
8
9
char *buf;
int len;
faacEncGetDecoderSpecificInfo(fh,&buf,&len);
 
memcpy (spec_buf,buf,len);
spec_len = len;
 
/*釋放系統內存*/
free (buf);

 

另外如果你是打開 aac 文件進行發送,那么你可以嘗試自己計算這個值,其實也很簡單,打開faac 源代碼看一下 faacEncGetDecoderSpecificInfo 的實現,也就是幾個移位的事:)。

對於一般情況 44100Hz 雙聲道,這個值是 0x1210,偷懶就是直接用這個值吧。

4.5 AAC普通數據

如前面所述,發送 AAC 的普通數據要改造一下,因為 AAC 的前 7 個字節(包括幀界定符)對於 RTMP 服務器來說是無用的。

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void * cap_dialog_send_audio(unsigned char * buf, int len)
{
     long timeoffset;
     timeoffset = GetTickCount() - start_time;
 
     buf += 7;
     len += 7;
 
     if (len > 0) {
         RTMPPacket * packet;
         unsigned char * body;
 
         packet = (RTMPPacket *) malloc (RTMP_HEAD_SIZE+len+2);
         memset (packet,0,RTMP_HEAD_SIZE);
 
         packet->m_body = ( char *)packet + RTMP_HEAD_SIZE;
         body = (unsigned char *)packet->m_body;
 
         /*AF 01 + AAC RAW data*/
         body[0] = 0xAF;
         body[1] = 0x01;
         memcpy (&body[2],buf,len);
 
         packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
         packet->m_nBodySize = len+2;
         packet->m_nChannel = 0x04;
         packet->m_nTimeStamp = timeoffset;
         packet->m_hasAbsTimestamp = 0;
         packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
         packet->m_nInfoField2 = rtmp->m_stream_id;
 
         /*調用發送接口*/
         RTMP_SendPacket(rtmp,packet,TRUE);
         free (packet);
     }
     return 0;
}

至此所有流程均結束了。

要注意的幾件事:
libRTMP 多線程發送有時候可能會出現問題,不知道是什么問題,最好改成隊列發送。將填充好的 packet 通過消息或者其它方式發送給其它線程,發送線程統一發送即可。

5.參考文檔

  • 《Video File Format Specification Version 10》 http://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf
  • 《H264視頻通過RTMP直播》 http://blog.csdn.net/firehood_/article/details/8783589
  • rtmpdump-2.3


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM