libx264 版本是 128
libfaac 版本是 1.28 服務器
對於 H.264 而言每幀的界定符爲 00 00 00 01 或者 00 00 01。 多線程
好比下面的 h264 文件片段這就包含三幀數據: ide
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, url
咱們發送 RTMP 數據時只須要知道四種幀類型,其它類型我都把它規類成非關鍵幀。分別是
NAL_SPS(7), sps 幀
NAL_PPS(8), pps 幀
NAL_SLICE_IDR(5), 關鍵幀
NAL_SLICE(1) 非關鍵幀 spa
幀類型的方式判斷爲界面符後首字節的低四位。
第一幀的幀類型爲: 0x67 & 0x1F = 7,這是一個 SPS 幀
第二幀的幀類型爲: 0x68 & 0x1F = 8,這是一個 PPS 幀
第三幀的幀類型爲: 0x06 & 0x1F = 6,這是一個 SEI 幀 .net
以上是咱們利用幀界定符劃分幀,並能夠判斷每個幀的類型。 線程
注意:若是是壓縮圖像成 H264 幀,咱們就可沒必要進行幀界定,由於每一次壓縮的輸出都明確了該幀的大小(包括界定符),每一次的壓縮的結果可能包函多幀。一會具體討論。 code
對於 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 文件,那咱們就要對幀進行界定。
若是咱們只是簡答的將壓縮數據打包發送給 RTMP 服務器,那麼 RTMP 服務器是不能夠對數據進行解碼和播放的,在這以前咱們要將音視頻的視頻的編碼信息發送給 RTMP 服務器。不少人可能苦於尋找下面的三個編碼參數而不得要領。其實要想獲得也是很簡單的。
對於 H264 而言,SPS 就是編碼後的第一幀。若是是讀取 H264 文件,就是第一個幀界定符與第二幀界定符中間的數據長度是 4。
對於 H264 而言,PPS 就是編碼後的第二幀。若是是讀取 H264 文件,就是第二個幀界定符與第三幀界定符中間的數據,長度不固定。
這個長度爲 2 個字節,能夠經過計算或者調用函數獲取。建議經過調用faacEncGetDecoderSpecificInfo(fh,&spec,&len); 獲取。
通常狀況雙聲道 44100 採樣下,該值是 0x1210
/*分配與初始化*/ 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; }
/*定義包頭長度,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);
/*關閉與釋放*/ RTMP_Close(rtmp); RTMP_Free(rtmp);
H.264 的編碼信息幀是發送給 RTMP 服務器稱爲 AVC sequence header,RTMP 服務器只有收到 AVC sequence header 中的 sps, pps 才能解析後續發送的 H264 幀。
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 幀。
咱們在編碼時會調用以下接口
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 的具體意義:)
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幀。
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 的方法
char *buf; int len; faacEncGetDecoderSpecificInfo(fh,&buf,&len); memcpy(spec_buf,buf,len); spec_len = len; /*釋放系統內存*/ free(buf);
另外若是你是打開 aac 文件進行發送,那麼你能夠嘗試本身計算這個值,其實也很簡單,打開faac 源代碼看一下 faacEncGetDecoderSpecificInfo 的實現,也就是幾個移位的事:)。
對於通常狀況 44100Hz 雙聲道,這個值是 0x1210,偷懶就是直接用這個值吧。
如前面所述,發送 AAC 的普通數據要改造一下,由於 AAC 的前 7 個字節(包括幀界定符)對於 RTMP 服務器來講是無用的。
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 經過消息或者其它方式發送給其它線程,發送線程統一發送便可。