文章目錄
安卓直播推流專欄博客總結
0 . 資源和源碼地址 :
- 資源下載地址 : 資源下載地址 , 服務器搭建 , x264 , faac , RTMPDump , 源碼及交叉編譯庫 , 本專欄 Android 直播推流源碼 ;
- GitHub 源碼地址 : han1202012 / RTMP_Pusher
1. 搭建 RTMP 服務器 : 下面的博客中講解了如何在 VMWare 虛擬機中搭建 RTMP 直播推流服務器 ;
2. 準備視頻編碼的 x264 編碼器開源庫 , 和 RTMP 數據包封裝開源庫 :
-
【Android RTMP】RTMPDumb 源碼導入 Android Studio ( 交叉編譯 | 配置 CMakeList.txt 構建腳本 )
-
【Android RTMP】Android Studio 集成 x264 開源庫 ( Ubuntu 交叉編譯 | Android Studio 導入函數庫 )
3. 講解 RTMP 數據包封裝格式 :
4. 圖像數據採集 : 從 Camera 攝像頭中採集 NV21 格式的圖像數據 , 並預覽該數據 ;
-
【Android RTMP】Android Camera 視頻數據採集預覽 ( 視頻採集相關概念 | 攝像頭預覽參數設置 | 攝像頭預覽數據回調接口 )
-
【Android RTMP】Android Camera 視頻數據採集預覽 ( NV21 圖像格式 | I420 圖像格式 | NV21 與 I420 格式對比 | NV21 轉 I420 算法 )
-
【Android RTMP】Android Camera 視頻數據採集預覽 ( 圖像傳感器方向設置 | Camera 使用流程 | 動態權限申請 )
5. NV21 格式的圖像數據編碼成 H.264 格式的視頻數據 :
-
【Android RTMP】x264 編碼器初始化及設置 ( 獲取 x264 編碼參數 | 編碼規格 | 碼率 | 幀率 | B幀個數 | 關鍵幀間隔 | 關鍵幀解碼數據 SPS PPS )
-
【Android RTMP】x264 圖像數據編碼 ( Camera 圖像數據採集 | NV21 圖像數據傳到 Native 處理 | JNI 傳輸字節數組 | 局部引用變量處理 | 線程互斥 )
-
【Android RTMP】x264 圖像數據編碼 ( NV21 格式中的 YUV 數據排列 | Y 灰度數據拷貝 | U 色彩值數據拷貝 | V 飽和度數據拷貝 | 圖像編碼操作 )
6. 將 H.264 格式的視頻數據封裝到 RTMP 數據包中 :
-
【Android RTMP】RTMPDump 封裝 RTMPPacket 數據包 ( 封裝 SPS / PPS 數據包 )
-
【Android RTMP】RTMPDump 封裝 RTMPPacket 數據包 ( 關鍵幀數據格式 | 非關鍵幀數據格式 | x264 編碼後的數據處理 | 封裝 H.264 視頻數據幀 )
-
【Android RTMP】RTMPDump 推流過程 ( 獨立線程推流 | 創建推流器 | 初始化操作 | 設置推流地址 | 啓用寫出 | 連接 RTMP 服務器 | 發送 RTMP 數據包 )
7. 階段總結 : 阿里雲服務器中搭建 RTMP 服務器 , 並使用電腦軟件推流和觀看直播內容 ;
-
【Android RTMP】RTMP 直播推流 ( 阿里雲服務器購買 | 遠程服務器控制 | 搭建 RTMP 服務器 | 服務器配置 | 推流軟件配置 | 直播軟件配置 | 推流直播效果展示 )
-
【Android RTMP】RTMP 直播推流階段總結 ( 服務器端搭建 | Android 手機端編碼推流 | 電腦端觀看直播 | 服務器狀態查看 )
8. 處理 Camera 圖像傳感器導致的 NV21 格式圖像旋轉問題 :
-
【Android RTMP】NV21 圖像旋轉處理 ( 問題描述 | 圖像順時針旋轉 90 度方案 | YUV 圖像旋轉細節 | 手機屏幕旋轉方向 )
-
【Android RTMP】NV21 圖像旋轉處理 ( 圖像旋轉算法 | 後置攝像頭順時針旋轉 90 度 | 前置攝像頭順時針旋轉 90 度 )
9. 下面這篇博客比較重要 , 裏面有一個快速搭建 RTMP 服務器的腳本 , 強烈建議使用 ;
10. 編碼 AAC 音頻數據的開源庫 FAAC 交叉編譯與 Android Studio 環境搭建 :
-
【Android RTMP】音頻數據採集編碼 ( 音頻數據採集編碼 | AAC 高級音頻編碼 | FAAC 編碼器 | Ubuntu 交叉編譯 FAAC 編碼器 )
-
【Android RTMP】音頻數據採集編碼 ( FAAC 頭文件與靜態庫拷貝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音頻採樣 PCM 格式 )
11. 解析 AAC 音頻格式 :
12 . 將麥克風採集的 PCM 音頻採樣編碼成 AAC 格式音頻 , 並封裝到 RTMP 包中 , 推流到客戶端 :
-
【Android RTMP】音頻數據採集編碼 ( FAAC 音頻編碼參數設置 | FAAC 編碼器創建 | 獲取編碼器參數 | 設置 AAC 編碼規格 | 設置編碼器輸入輸出參數 )
-
【Android RTMP】音頻數據採集編碼 ( FAAC 編碼器編碼 AAC 音頻解碼信息 | 封裝 RTMP 音頻數據頭 | 設置 AAC 音頻數據類型 | 封裝 RTMP 數據包 )
-
【Android RTMP】音頻數據採集編碼 ( FAAC 編碼器編碼 AAC 音頻採樣數據 | 封裝 RTMP 音頻數據頭 | 設置 AAC 音頻數據類型 | 封裝 RTMP 數據包 )
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 視頻數據 ;
一、 NV21 圖像數據中的 YUV 數據簡介
Camera 採集的數據是 NV21 格式的 ;
NV21 是 YUV 格式中的一種 , Y 代表灰度 , U 代表色彩值 , V 代表色彩的飽和度 ;
NV21 格式數據在內存中的表示 : 以 大小的圖片爲例 , 先存放 個像素的灰度值 Y 數據 , 然後 組色彩值 V 數據和飽和度 U 數據交替存放 ;
byte[] data = {
y1 , y2 , y3 , y4 ,
y5 , y6 , y7 , y8 ,
y9 , y10, y11, y12,
y13, y14, y15, y16,
v1 , u1 , v2 , u2 ,
v3 , u3 , v4 , u4 ,
}
【Android RTMP】Android Camera 視頻數據採集預覽 ( NV21 圖像格式 | I420 圖像格式 | NV21 與 I420 格式對比 | NV21 轉 I420 算法 ) 博客中詳細介紹了 NV21 數據中的 YUV 數據格式 ;
二、向 x264 編碼圖片
1 . x264 編碼圖片引入 : x264 編碼器對圖像數據進行編碼 , 要先將 NV21 的圖像數據中的 YUV 數據分別存儲到 x264 編碼圖片中 ;
2 . x264_picture_t 結構體 : 該結構體代表了 x264 編碼圖片 , 該結構體定義在 x264.h 中 ;
typedef struct x264_picture_t
{
// ...
// 存儲要編碼的圖片數據
x264_image_t img;
// ...
} x264_picture_t;
3 . x264 編碼圖片使用 :
① 聲明 x264_picture_t 指針變量 : C++ 堆內存中的對象必須使用指針接收 ;
// x264 需要編碼的圖片
x264_picture_t *x264EncodePicture = 0;
② 初始化 x264_picture_t 對象 : 首先創建 x264_picture_t 對象 , 設置編碼方式爲 I420 , 以及圖片的寬度 x264Param.i_width , 和圖片高度 x264Param.i_height ;
// 初始化 x264 編碼圖片
x264EncodePicture = new x264_picture_t;
// 爲 x264 編碼圖片分配內存
x264_picture_alloc(x264EncodePicture, X264_CSP_I420, x264Param.i_width, x264Param.i_height);
③ 釋放 x264_picture_t 對象 : 調用 x264_picture_clean 方法釋放資源 , 然後銷燬對象 ;
// 只要調用該方法, x264_picture_t 必須重新進行初始化
// 因爲圖片大小改變了, 那麼對應的圖片不能再使用原來的參數了
// 釋放原來的 x264_picture_t 圖片, 重新進行初始化
// 析構函數中也要進行釋放
if (x264EncodePicture) {
x264_picture_clean(x264EncodePicture);
delete x264EncodePicture;
x264EncodePicture = 0;
}
三、 提取 NV21 數據中的灰度數據 Y
1 . 計算灰度數據的個數 : 灰度數據的個數 , 就是像素的個數 , 每個像素點都有一個灰度數據 ;
// 灰色值的個數, 單位字節
YByteCount = width * height;
2 . 將灰度數據存儲到 x264_picture_t 中 : 在 NV21 格式的圖像數據中 , 前 YByteCount 個數據是 YByteCount 個像素點的灰度數據 , 將這些灰度數據拷貝到 x264 編碼圖像中 ;
3 . 數據接收方 : x264_picture_t* x264EncodePicture 圖像的 img 成員的 plane[0] 指針指向的地址 , 接收 YByteCount 個灰度數據 ;
4 . 代碼示例 :
// 從 Camera 採集的 NV21 格式的 data 數據中
// 將 YUV 中的 Y 灰度值數據, U 色彩值數據, V 色彩飽和度數據提取出來
memcpy(x264EncodePicture->img.plane[0], data, YByteCount);
四、 提取 NV21 數據中的飽和度數據 U 和 色彩值數據 V
1 . 計算飽和度數據 U 的個數 : 飽和度數據 U 的個數 , 與色彩值數據 V 的個數相同 , 是灰度值數據 Y 個數的 ;
// 灰色值的個數, 單位字節
YByteCount = width * height;
// U 色彩值, V 飽和度 個數
UVByteCount = YByteCount / 4;
2 . 將灰度數據存儲到 x264_picture_t 中 : 在 NV21 格式的圖像數據中 , 色彩值數據 V , 飽和度數據 U , 交替存儲 , V 在前 ( 偶數位置 ), U 在後 ( 奇數位置 ) ;
① U 色相 / 色彩值數據 : 存儲在 YByteCount 後的奇數索引位置
② V 色彩飽和度數據 : 存儲在 YByteCount 後的偶數索引位置
3 . 代碼示例 :
// 取出 NV21 數據中交替存儲的 VU 數據
// V 在前 ( 偶數位置 ), U 在後 ( 奇數位置 ), 交替存儲
for(int i = 0; i < UVByteCount; i ++){
// U 色相 / 色彩值數據, 存儲在 YByteCount 後的奇數索引位置
*(x264EncodePicture->img.plane[1] + i) = *(data + YByteCount + i * 2 + 1);
// V 色彩飽和度數據, 存儲在 YByteCount 後的偶數索引位置
*(x264EncodePicture->img.plane[2] + i) = *(data + YByteCount + i * 2);
}
五、 圖像編碼操作
1 . 圖片編碼 :
① 普通幀 : 一般情況下, 一張圖像編碼出一幀數據 , pp_nal 是一幀數據, pi_nal 表示幀數爲 1
② 關鍵幀 : 如果這個幀是關鍵幀, 那麼 pp_nal 將會編碼出 3 幀數據 , pi_nal 表示幀數爲 3
③ 關鍵幀數據 : SPS 幀, PPS 幀, 畫面幀 ;
2 . 編碼方法函數原型 :
int x264_encoder_encode( x264_t *, x264_nal_t **pp_nal, int *pi_nal,
x264_picture_t *pic_in, x264_picture_t *pic_out );
① x264_t * 參數 : x264 視頻編碼器
② x264_nal_t **pp_nal 參數 : 編碼後的幀數據, 可能是 1 幀, 也可能是 3 幀
③ int *pi_nal 參數 : 編碼後的幀數, 1 或 3
④ x264_picture_t *pic_in 參數 : 輸入的 NV21 格式的圖片數據
⑤ x264_picture_t *pic_out 參數 : 輸出的圖片數據
3 . 圖像編碼代碼示例 :
// 編碼後的數據, 這是一個幀數據, 1 幀或 3幀
x264_nal_t *pp_nal;
// 編碼後的數據個數, 幀的個數, 1 或 3
int pi_nal;
// 輸出的圖片數據
x264_picture_t pic_out;
// 編碼核心操作
x264_encoder_encode(x264VedioCodec, &pp_nal, &pi_nal, x264EncodePicture, &pic_out);
六、 x264 視頻數據編碼代碼示例
x264 編碼器將 NV21 圖像數據編碼爲 H.264 代碼 :
/**
* 視頻數據編碼
* 接收 int8_t 類型的原因是, 這裏處理的是 jbyte* 類型參數
* jbyte 類型就是 int8_t 類型
* @param data 視頻數據指針
*/
void VedioChannel::encodeCameraData(int8_t *data) {
// 加鎖, 設置視頻編碼參數 與 編碼互斥
pthread_mutex_lock(&mMutex);
// 參數中的 data 是 NV21 格式的
// 前面 YByteCount 字節個 Y 灰度數據
// 之後是 UVByteCount 字節個 VU 數據交替存儲
// UVByteCount 字節 V 數據, UVByteCount 字節 U 數據
// 從 Camera 採集的 NV21 格式的 data 數據中
// 將 YUV 中的 Y 灰度值數據, U 色彩值數據, V 色彩飽和度數據提取出來
memcpy(x264EncodePicture->img.plane[0], data, YByteCount);
// 取出 NV21 數據中交替存儲的 VU 數據
// V 在前 ( 偶數位置 ), U 在後 ( 奇數位置 ), 交替存儲
for(int i = 0; i < UVByteCount; i ++){
// U 色相 / 色彩值數據, 存儲在 YByteCount 後的奇數索引位置
*(x264EncodePicture->img.plane[1] + i) = *(data + YByteCount + i * 2 + 1);
// V 色彩飽和度數據, 存儲在 YByteCount 後的偶數索引位置
*(x264EncodePicture->img.plane[2] + i) = *(data + YByteCount + i * 2);
}
// 下面兩個是編碼時需要傳入的參數, 這兩個參數地址, x264 編碼器會想這兩個地址寫入值
// 編碼後的數據, 這是一個幀數據
x264_nal_t *pp_nal;
// 編碼後的數據個數, 幀的個數
int pi_nal;
// 輸出的圖片數據
x264_picture_t pic_out;
/*
int x264_encoder_encode( x264_t *, x264_nal_t **pp_nal, int *pi_nal,
x264_picture_t *pic_in, x264_picture_t *pic_out );
函數原型 :
x264_t * 參數 : x264 視頻編碼器
x264_nal_t **pp_nal 參數 : 編碼後的幀數據, 可能是 1 幀, 也可能是 3 幀
int *pi_nal 參數 : 編碼後的幀數, 1 或 3
x264_picture_t *pic_in 參數 : 輸入的 NV21 格式的圖片數據
x264_picture_t *pic_out 參數 : 輸出的圖片數據
普通幀 : 一般情況下, 一張圖像編碼出一幀數據, pp_nal 是一幀數據, pi_nal 表示幀數爲 1
關鍵幀 : 如果這個幀是關鍵幀, 那麼 pp_nal 將會編碼出 3 幀數據, pi_nal 表示幀數爲 3
關鍵幀數據 : SPS 幀, PPS 幀, 畫面幀
*/
x264_encoder_encode(x264VedioCodec, &pp_nal, &pi_nal, x264EncodePicture, &pic_out);
// 後續還有操作, 本博客中暫時省略 ...
// 解鎖, 設置視頻編碼參數 與 編碼互斥
pthread_mutex_unlock(&mMutex);
}