【Android RTMP】RTMPDump 推流过程 ( 独立线程推流 | 创建推流器 | 初始化操作 | 设置推流地址 | 启用写出 | 连接 RTMP 服务器 | 发送 RTMP 数据包 )
文章目錄
- 安卓直播推流專欄博客總結
- 一、 Java 層傳入的 RTMP 推流地址處理
- 二、 RTMPDump 推流線程
- 三、 創建 RTMP 對象
- 四、 初始化 RTMP 對象
- 五、 設置 RTMP 推流地址
- 六、 啟用 RTMP 寫出功能
- 七、 連接 RTMP 服務器
- 八、 連接 RTMP 流
- 九、 發送 RTMP 數據包
- 十、 斷開 RTMP 連接并釋放資源
- 十一、 RTMPDump 推流代碼
安卓直播推流專欄博客總結
Android RTMP 直播推流技術專欄 :
0 . 資源和源碼地址 :
- 資源下載地址 : 資源下載地址 , 服務器搭建 , x264 , faac , RTMPDump , 源碼及交叉編譯庫 , 本專欄 Android 直播推流源碼 ;
- GitHub 源碼地址 : han1202012 / RTMP_Pusher
1. 搭建 RTMP 服務器 : 下面的博客中講解了如何在 VMWare 虛擬機中搭建 RTMP 直播推流服務器 ;
- 【Android RTMP】RTMP 直播推流服務器搭建 ( Ubuntu 18.04.4 虛擬機 )
2. 準備視頻編碼的 x264 編碼器開源庫 , 和 RTMP 數據包封裝開源庫 :
-
【Android RTMP】RTMPDumb 源碼導入 Android Studio ( 交叉編譯 | 配置 CMakeList.txt 構建腳本 )
-
【Android RTMP】Android Studio 集成 x264 開源庫 ( Ubuntu 交叉編譯 | Android Studio 導入函數庫 )
3. 講解 RTMP 數據包封裝格式 :
-
【Android RTMP】RTMP 數據格式 ( FLV 視頻格式分析 | 文件頭 Header 分析 | 標簽 Tag 分析 | 視頻標簽 Tag 數據分析 )
-
【Android RTMP】RTMP 數據格式 ( FLV 視頻格式分析 | AVC 序列頭格式解析 )
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 服務器的腳本 , 強烈建議使用 ;
- 【Android RTMP】NV21 圖像旋轉處理 ( 快速搭建 RTMP 服務器 Shell 腳本 | 創建 RTMP 服務器鏡像 | 瀏覽器觀看直播 | 前置 / 后置攝像頭圖像旋轉效果展示 )
10. 編碼 AAC 音頻數據的開源庫 FAAC 交叉編譯與 Android Studio 環境搭建 :
-
【Android RTMP】音頻數據采集編碼 ( 音頻數據采集編碼 | AAC 高級音頻編碼 | FAAC 編碼器 | Ubuntu 交叉編譯 FAAC 編碼器 )
-
【Android RTMP】音頻數據采集編碼 ( FAAC 頭文件與靜態庫拷貝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音頻采樣 PCM 格式 )
11. 解析 AAC 音頻格式 :
- 【Android RTMP】音頻數據采集編碼 ( AAC 音頻格式解析 | FLV 音頻數據標簽解析 | 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 服務器中 ;
本篇博客中將介紹 , 使用 RTMPDump 開源庫 , 將編碼好的 RTMP 數據包 , 推送到遠程 RTMP 服務器 ; 即 RTMPDump 推流過程 ;
一、 Java 層傳入的 RTMP 推流地址處理
1 . Java 傳遞字符串數據到 JNI : 啟動推流時 , Java 層會將 RTMP 推流地址傳遞給 JNI ;
2 . jstring 類型轉為 char* 類型 : 將 Java 字符串轉為 C 字符串 ;
// 獲取 Rtmp 推流地址 // 該 pushPathFromJava 引用是局部引用, 超過作用域就無效了 // 局部引用不能跨方法 , 跨線程調用 const char* pushPathFromJava = env->GetStringUTFChars(path, 0);3 . 局部引用變量處理 : 該轉換后的 const char* pushPathFromJava 字符串是局部引用變量 , 不能跨進程 , 跨作用域使用 , 之后的推流操作在獨立的線程中使用 , 因此需要將字符串數據在堆內存中存儲 ;
// 獲取地址的長度, 加上 '\0' 長度 char * pushPathNative = new char[strlen(pushPathFromJava) + 1]; // 拷貝 pushPathFromJava 到堆內存 pushPathNative 中 // 局部引用不能跨方法 , 跨線程調用, 這里需要在線程中使用該地址 // 因此需要將該局部引用拷貝到堆內存中, 然后傳遞到對應線程中 strcpy(pushPathNative, pushPathFromJava);4 . 釋放局部引用 : JNI 中的局部引用變量 , 使用完畢后及時釋放 ;
// 釋放從 Java 層獲取的字符串 // 釋放局部引用 env->ReleaseStringUTFChars(path, pushPathFromJava);二、 RTMPDump 推流線程
1 . 獨立線程推流 : RTMP 推流操作需要在一個獨立的線程中完成 , 涉及到網絡的操作都是耗時操作 , 在 Android 中都要在線程中執行 ;
2 . 線程 ID 聲明 : 需要導入 #include <pthread.h> 包 , 之后才能使用線程 , 先聲明線程 ID , pthread_t 類型 ;
/*** 開始推流工作線程的線程 ID*/ pthread_t startRtmpPushPid;3 . 創建并執行線程 : 創建并執行線程 , 在線程中執行 startRtmpPush 方法 , 傳入 pushPathNative 字符串參數 ;
// 創建線程 pthread_create(&startRtmpPushPid, 0, startRtmpPush, pushPathNative);4 . 線程方法 : 定義線程方法 , 參數和返回值都是 void* 類型 , 在開始位置獲取傳入的參數 ;
void* startRtmpPush (void* args){// 0. 獲取 Rtmp 推流地址char* pushPath = static_cast<char *>(args);// ... }三、 創建 RTMP 對象
創建 RTMP 對象 , 如果創建失敗 , 直接停止整個推流方法 ;
// 1. 創建 RTMP 對象, 申請內存 rtmp = RTMP_Alloc(); if (!rtmp) {__android_log_print(ANDROID_LOG_INFO, "RTMP", "申請 RTMP 內存失敗");break; }四、 初始化 RTMP 對象
初始化 RTMP 對象 , 并設置超時時間 ;
// 2. 初始化 RTMP RTMP_Init(rtmp); // 設置超時時間 5 秒 rtmp->Link.timeout = 5;五、 設置 RTMP 推流地址
設置 RTMP 推流地址 , 如果設置失敗 , 直接退出推流操作 ;
該地址就是 Java 層傳給 JNI 的字符串 , 剛獲取時是局部引用變量 , 將其拷貝到了堆內存中 , 才可以在推流線程中使用 ;
// 3. 設置 RTMP 推流服務器地址 int ret = RTMP_SetupURL(rtmp, pushPath); if (!ret) {__android_log_print(ANDROID_LOG_INFO, "RTMP", "設置 RTMP 推流服務器地址 %s 失敗", pushPath);break; }六、 啟用 RTMP 寫出功能
啟用 RTMP 寫出功能 ;
// 4. 啟用 RTMP 寫出功能 RTMP_EnableWrite(rtmp);七、 連接 RTMP 服務器
連接 RTMP 服務器 , 如果連接失敗 , 直接退出該方法 ;
// 5. 連接 RTMP 服務器 ret = RTMP_Connect(rtmp, 0); if (!ret) {__android_log_print(ANDROID_LOG_INFO, "RTMP", "連接 RTMP 服務器 %s 失敗", pushPath);break; }八、 連接 RTMP 流
連接 RTMP 流 , 如果連接失敗 , 退出方法 ;
// 6. 連接 RTMP 流 ret = RTMP_ConnectStream(rtmp, 0); if (!ret) {__android_log_print(ANDROID_LOG_INFO, "RTMP", "連接 RTMP 流 %s 失敗", pushPath);break; }九、 發送 RTMP 數據包
將 RTMP 數據包發送到服務器中 ;
// 7. 將 RTMP 數據包發送到服務器中 ret = RTMP_SendPacket(rtmp, packet, 1);十、 斷開 RTMP 連接并釋放資源
推流結束后 , 關閉與 RTMP 服務器連接 , 釋放資源 ;
// 8. 推流結束, 關閉與 RTMP 服務器連接, 釋放資源 if(rtmp){RTMP_Close(rtmp);RTMP_Free(rtmp); }十一、 RTMPDump 推流代碼
RTMPDump 推流代碼 :
/*** 開始向遠程 RTMP 服務器推送數據*/ extern "C" JNIEXPORT void JNICALL Java_kim_hsl_rtmp_LivePusher_native_1startRtmpPush(JNIEnv *env, jobject thiz,jstring path) {if(isStartRtmpPush){// 防止該方法多次調用, 如果之前調用過, 那么屏蔽本次調用return;}// 執行過一次后, 馬上標記已執行狀態, 下一次就不再執行該方法了isStartRtmpPush = TRUE;// 獲取 Rtmp 推流地址// 該 pushPathFromJava 引用是局部引用, 超過作用域就無效了// 局部引用不能跨方法 , 跨線程調用const char* pushPathFromJava = env->GetStringUTFChars(path, 0);// 獲取地址的長度, 加上 '\0' 長度char * pushPathNative = new char[strlen(pushPathFromJava) + 1];// 拷貝 pushPathFromJava 到堆內存 pushPathNative 中// 局部引用不能跨方法 , 跨線程調用, 這里需要在線程中使用該地址// 因此需要將該局部引用拷貝到堆內存中, 然后傳遞到對應線程中strcpy(pushPathNative, pushPathFromJava);// 創建線程pthread_create(&startRtmpPushPid, 0, startRtmpPush, pushPathNative);// 釋放從 Java 層獲取的字符串// 釋放局部引用env->ReleaseStringUTFChars(path, pushPathFromJava); }/*** 開始推流任務線程* 主要是調用 RTMPDump 進行推流* @param args* @return*/ void* startRtmpPush (void* args){// 0. 獲取 Rtmp 推流地址char* pushPath = static_cast<char *>(args);// rtmp 推流器RTMP* rtmp = 0;// rtmp 推流數據包RTMPPacket *packet = 0;/*將推流核心執行內容放在 do while 循環中在出錯后, 隨時 break 退出循環, 執行后面的釋放資源的代碼可以保證, 在最后將資源釋放掉, 避免內存泄漏避免執行失敗, 直接 return, 導致資源沒有釋放*/do {// 1. 創建 RTMP 對象, 申請內存rtmp = RTMP_Alloc();if (!rtmp) {__android_log_print(ANDROID_LOG_INFO, "RTMP", "申請 RTMP 內存失敗");break;}// 2. 初始化 RTMPRTMP_Init(rtmp);// 設置超時時間 5 秒rtmp->Link.timeout = 5;// 3. 設置 RTMP 推流服務器地址int ret = RTMP_SetupURL(rtmp, pushPath);if (!ret) {__android_log_print(ANDROID_LOG_INFO, "RTMP", "設置 RTMP 推流服務器地址 %s 失敗", pushPath);break;}// 4. 啟用 RTMP 寫出功能RTMP_EnableWrite(rtmp);// 5. 連接 RTMP 服務器ret = RTMP_Connect(rtmp, 0);if (!ret) {__android_log_print(ANDROID_LOG_INFO, "RTMP", "連接 RTMP 服務器 %s 失敗", pushPath);break;}// 6. 連接 RTMP 流ret = RTMP_ConnectStream(rtmp, 0);if (!ret) {__android_log_print(ANDROID_LOG_INFO, "RTMP", "連接 RTMP 流 %s 失敗", pushPath);break;}// 準備推流相關的數據, 如線程安全隊列readyForPush = TRUE;// 記錄推流開始時間pushStartTime = RTMP_GetTime();// 線程安全隊列開始工作packets.setWork(1);while (isStartRtmpPush) {// 從線程安全隊列中// 取出一包已經打包好的 RTMP 數據包packets.pop(packet);// 確保當前處于推流狀態if (!isStartRtmpPush) {break;}// 確保不會取出空的 RTMP 數據包if (!packet) {continue;}// 設置直播的流 IDpacket->m_nInfoField2 = rtmp->m_stream_id;// 7. 將 RTMP 數據包發送到服務器中ret = RTMP_SendPacket(rtmp, packet, 1);// RTMP 數據包使用完畢后, 釋放該數據包if (packet) {RTMPPacket_Free(packet);delete packet;packet = 0;}if (!ret) {__android_log_print(ANDROID_LOG_INFO, "RTMP", "RTMP 數據包推流失敗");break;}}}while (0);// 面的部分是收尾部分, 釋放資源// 8. 推流結束, 關閉與 RTMP 服務器連接, 釋放資源if(rtmp){RTMP_Close(rtmp);RTMP_Free(rtmp);}// 推流數據包 線程安全隊列釋放// 防止中途退出導致沒有釋放資源, 造成內存泄漏if (packet) {RTMPPacket_Free(packet);delete packet;packet = 0;}// 釋放推流地址if(pushPath){delete pushPath;pushPath = 0;}return 0; }總結
以上是生活随笔為你收集整理的【Android RTMP】RTMPDump 推流过程 ( 独立线程推流 | 创建推流器 | 初始化操作 | 设置推流地址 | 启用写出 | 连接 RTMP 服务器 | 发送 RTMP 数据包 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android RTMP】RTMPDu
- 下一篇: 【错误记录】Android NDK 错误