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