文章目錄
Android 直播推流流程 : 手機採集視頻 / 音頻數據 , 視頻數據使用 H.264 編碼 , 音頻數據使用 AAC 編碼 , 最後將音視頻數據都打包到 RTMP 數據包中 , 使用 RTMP 協議上傳到 RTMP 服務器中 ;
Android 端中主要完成手機端採集視頻數據操作 , 並將視頻數據傳遞給 JNI , 在 NDK 中使用 x264 將圖像轉爲 H.264 格式的視頻 , 最後將 H.264 格式的視頻打包到 RTMP 數據包中 , 上傳到 RTMP 服務器中 ;
本篇博客中介紹如下內容 , Java 層將 Camera 採集的 NV21 格式的數據傳入 JNI 層 , 在 JNI 中使用 x264 編碼器將 NV21 圖像數據編碼爲 H.264 視頻數據 ;
本篇博客中主要封裝 AVC 序列頭數據 , 將 幀類型 , AVC 數據類型 , 合成時間 , 版本信息 , 編碼規格 , NALU 長度 , SPS 個數 , SPS 長度 , SPS 數據 , PPS 個數 , PPS 長度 , PPS 數據 , 封裝到 RTMP 包中 ;
一、 基本封裝數據格式說明
1 . 這是完整的視頻標籤數據內容 : 這是 FLV 中完整視頻標籤數據 ;
0x00000182 : 09 00 00 2E 00 00 00 00
0x0000018a : 00 00 00 17 00 00 00 00
0x00000192 : 01 64 00 32 FF E1 00 19
0x0000019a : 67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68 01 00 05 68 E9 7B 2C
0x000001ba : 8B 00 00 00 39
2 . 標籤頭 : 前 個字節是標籤頭數據 , 存儲有 標籤類型 , 標籤數據大小 , 時間戳 , 時間戳擴展位 , 流編號 等 字節信息 ;
0x00000182 : 09 00 00 2E 00 00 00 00
0x0000018a : 00 00 00
3 . 標籤數據 ( 重點 ) : 這就是本篇博客要封裝的內容 , 基本上是封裝一個格式一模一樣的 RTMP 數據包 ,
17 00 00 00 00
0x00000192 : 01 64 00 32 FF E1 00 19
0x0000019a : 67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68 01 00 05 68 E9 7B 2C
0x000001ba : 8B 00 00 00 39
參考博客 : 參考之前的兩篇分析 RTMP 數據格式的博客 , 分析了與 RTMP 格式幾乎一致的 FLV 視頻數據格式 ;
這兩篇博客一定要 , 並且明白 FLV 視頻標籤數據格式 , 才能看懂今天寫的 RTMP 數據包封裝的內容 ;
二、 封裝 SPS PPS 數據總體說明
1 . 數據示例 :
17 00 00 00 00
0x00000192 : 01 64 00 32 FF E1 00 19
0x0000019a : 67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68 01 00 05 68 E9 7B 2C
0x000001ba : 8B 00 00 00 39
- 17 幀類型, 1 字節
- 00 數據類型, 1 字節
- 00 00 00 合成時間, 3 字節
- 01 版本信息, 1 字節
- 64 00 32 編碼規則, 3 字節
- FF NALU 長度, 1 字節
- E1 SPS 個數, 1 字節
- 00 19 SPS 長度, 2 字節
截止到當前位置有 13 字節數據
- spsLen 字節數據, 這裏是 25 字節
67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68
- 01 PPS 個數, 1 字節
- 00 05 PPS 長度, 2 字節
- ppsLen 字節的 PPS 數據
68 E9 7B 2C
0x000001ba : 8B
- 後面的 00 00 00 39 是視頻標籤的總長度 , 這裏在 RTMP 標籤中可以不用封裝 ;
2 . 計算整個 SPS 和 PPS 數據的大小 :
① 封裝頭 : 幀類型 , 數據類型 , 合成時間 , 版本信息 , 編碼規則 , NALU 長度 , 總共有 字節 ;
② 封裝 SPS 數據 : SPS 個數 , SPS 長度 , SPS 數據 , 分別有 字節 ;
③ 封裝 PPS 數據 : PPS 個數 , PPS 長度 , PPS 數據 , 分別有 字節 ;
int rtmpPackagesize = 10 + 3 + spsLen + 3 + ppsLen;
三、 封裝頭數據
向 RTMP 數據包中 , 封裝 幀類型 , 數據類型 , 合成時間 , 版本信息 , 編碼規則 , NALU 長度 , 總共有 字節 ;
// 幀類型數據 : 分爲兩部分;
// 前 4 位表示幀類型, 1 表示關鍵幀, 2 表示普通幀
// 後 4 位表示編碼類型, 7 表示 AVC 視頻編碼
rtmpPacket->m_body[nextPosition++] = 0x17;
// 數據類型, 00 表示 AVC 序列頭
rtmpPacket->m_body[nextPosition++] = 0x00;
// 合成時間, 一般設置 00 00 00
rtmpPacket->m_body[nextPosition++] = 0x00;
rtmpPacket->m_body[nextPosition++] = 0x00;
rtmpPacket->m_body[nextPosition++] = 0x00;
// 版本信息
rtmpPacket->m_body[nextPosition++] = 0x01;
// 編碼規格
rtmpPacket->m_body[nextPosition++] = sps[1];
rtmpPacket->m_body[nextPosition++] = sps[2];
rtmpPacket->m_body[nextPosition++] = sps[3];
// NALU 長度
rtmpPacket->m_body[nextPosition++] = 0xFF;
四、 封裝 SPS 數據
將 SPS 數據封裝到 RTMP 數據包中 , 包含 SPS 個數 , SPS 長度 , SPS 數據 ;
// SPS 個數
rtmpPacket->m_body[nextPosition++] = 0xE1;
// SPS 長度, 佔 2 字節
// 設置長度的高位
rtmpPacket->m_body[nextPosition++] = (spsLen >> 8) & 0xFF;
// 設置長度的低位
rtmpPacket->m_body[nextPosition++] = spsLen & 0xFF;
// 拷貝 SPS 數據
// 將 SPS 數據拷貝到 rtmpPacket->m_body[nextPosition] 地址中
memcpy(&rtmpPacket->m_body[nextPosition], sps, spsLen);
// 累加 SPS 長度信息
nextPosition += spsLen;
五、 封裝 PPS 數據
將 PPS 數據封裝到 RTMP 數據包中 , 包含 PPS 個數 , PPS 長度 , PPS 數據 ;
// PPS 個數
rtmpPacket->m_body[nextPosition++] = 0x01;
// PPS 數據的長度, 佔 2 字節
// 設置長度的高位
rtmpPacket->m_body[nextPosition++] = (ppsLen >> 8) & 0xFF;
// 設置長度的低位
rtmpPacket->m_body[nextPosition++] = (ppsLen) & 0xFF;
// 拷貝 SPS 數據
memcpy(&rtmpPacket->m_body[nextPosition], pps, ppsLen);
六、 設置 RTMP 數據包其它參數
設置 RTMP 包類型 , RTMP 包長度 , RTMP 通道 , 時間戳 等信息 ;
// 設置 RTMP 包類型, 視頻類型數據
rtmpPacket->m_packetType = RTMP_PACKET_TYPE_VIDEO;
// 設置 RTMP 包長度
rtmpPacket->m_nBodySize = rtmpPackagesize;
// 分配 RTMP 通道, 隨意分配
rtmpPacket->m_nChannel = 10;
// 設置視頻時間戳, 如果是 SPP PPS 數據, 沒有時間戳
rtmpPacket->m_nTimeStamp = 0;
// 設置絕對時間, 對於 SPS PPS 賦值 0 即可
rtmpPacket->m_hasAbsTimestamp = 0;
// 設置頭類型, 隨意設置一個
rtmpPacket->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
七、 SPS PPS 數據封裝代碼示例
/**
* 將 SPS / PPS 數據發送到 RTMP 服務器端
* @param sps SPS 數據
* @param pps PPS 數據
* @param spsLen SPS 長度
* @param ppsLen PPS 長度
*/
void VedioChannel::sendSpsPpsToRtmpServer(uint8_t *sps, uint8_t *pps, int spsLen, int ppsLen) {
// 創建 RTMP 數據包, 將數據都存入該 RTMP 數據包中
RTMPPacket *rtmpPacket = new RTMPPacket;
/*
計算整個 SPS 和 PPS 數據的大小
數據示例 :
17 00 00 00 00
0x00000192 : 01 64 00 32 FF E1 00 19
0x0000019a : 67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68 01 00 05 68 E9 7B 2C
0x000001ba : 8B 00 00 00 39
17 幀類型, 1 字節
00 數據類型, 1 字節
00 00 00 合成時間, 3 字節
01 版本信息, 1 字節
64 00 32 編碼規則, 3 字節
FF NALU 長度, 1 字節
E1 SPS 個數, 1 字節
00 19 SPS 長度, 2 字節
截止到當前位置有 13 字節數據
spsLen 字節數據, 這裏是 25 字節
67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68
01 PPS 個數, 1 字節
00 05 PPS 長度, 2 字節
ppsLen 字節的 PPS 數據
68 E9 7B 2C
0x000001ba : 8B
後面的 00 00 00 39 是視頻標籤的總長度
這裏再 RTMP 標籤中可以不用封裝
*/
int rtmpPackagesize = 10 + 3 + spsLen + 3 + ppsLen;
// 爲 RTMP 數據包分配內存
RTMPPacket_Alloc(rtmpPacket, rtmpPackagesize);
// 記錄下一個要寫入數據的索引位置
int nextPosition = 0;
// 幀類型數據 : 分爲兩部分;
// 前 4 位表示幀類型, 1 表示關鍵幀, 2 表示普通幀
// 後 4 位表示編碼類型, 7 表示 AVC 視頻編碼
rtmpPacket->m_body[nextPosition++] = 0x17;
// 數據類型, 00 表示 AVC 序列頭
rtmpPacket->m_body[nextPosition++] = 0x00;
// 合成時間, 一般設置 00 00 00
rtmpPacket->m_body[nextPosition++] = 0x00;
rtmpPacket->m_body[nextPosition++] = 0x00;
rtmpPacket->m_body[nextPosition++] = 0x00;
// 版本信息
rtmpPacket->m_body[nextPosition++] = 0x01;
// 編碼規格
rtmpPacket->m_body[nextPosition++] = sps[1];
rtmpPacket->m_body[nextPosition++] = sps[2];
rtmpPacket->m_body[nextPosition++] = sps[3];
// NALU 長度
rtmpPacket->m_body[nextPosition++] = 0xFF;
// SPS 個數
rtmpPacket->m_body[nextPosition++] = 0xE1;
// SPS 長度, 佔 2 字節
// 設置長度的高位
rtmpPacket->m_body[nextPosition++] = (spsLen >> 8) & 0xFF;
// 設置長度的低位
rtmpPacket->m_body[nextPosition++] = spsLen & 0xFF;
// 拷貝 SPS 數據
// 將 SPS 數據拷貝到 rtmpPacket->m_body[nextPosition] 地址中
memcpy(&rtmpPacket->m_body[nextPosition], sps, spsLen);
// 累加 SPS 長度信息
nextPosition += spsLen;
// PPS 個數
rtmpPacket->m_body[nextPosition++] = 0x01;
// PPS 數據的長度, 佔 2 字節
// 設置長度的高位
rtmpPacket->m_body[nextPosition++] = (ppsLen >> 8) & 0xFF;
// 設置長度的低位
rtmpPacket->m_body[nextPosition++] = (ppsLen) & 0xFF;
// 拷貝 SPS 數據
memcpy(&rtmpPacket->m_body[nextPosition], pps, ppsLen);
// 設置 RTMP 包類型, 視頻類型數據
rtmpPacket->m_packetType = RTMP_PACKET_TYPE_VIDEO;
// 設置 RTMP 包長度
rtmpPacket->m_nBodySize = rtmpPackagesize;
// 分配 RTMP 通道, 隨意分配
rtmpPacket->m_nChannel = 10;
// 設置視頻時間戳, 如果是 SPP PPS 數據, 沒有時間戳
rtmpPacket->m_nTimeStamp = 0;
// 設置絕對時間, 對於 SPS PPS 賦值 0 即可
rtmpPacket->m_hasAbsTimestamp = 0;
// 設置頭類型, 隨意設置一個
rtmpPacket->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
}