騰訊直播——推流SDK(Android)
功能篇
騰訊視訊雲RTMP SDK由兩部分構成:推流器 + 播放器,本文將主要介紹推流器的相關資訊。
該SDK遵循標準RTMP視訊推送協議,可以對接包括騰訊雲在內的標準視訊直播伺服器。與此同時,SDK內部囊括了騰訊音視訊團隊多年的技術積累,在視訊壓縮、硬體加速、美顏濾鏡、音訊降噪、位元速率控制等方面都做了很多的優化處理。
如果您是一位剛剛接觸視訊直播的合作伙伴,您只需要幾行程式碼就可以完成對接流程,而如果您是一位資深的移動端軟體開發工程師,SDK所提供的豐富的設定介面,亦可讓您能夠定製出最符合需求的表現。
SDK開發包附帶的推流器DEMO介面如下:
基礎篇
騰訊視訊雲RTMP SDK的使用特別簡單,您只需要在您的App裡新增如下幾行程式碼就可以完成對接工作了。目前SDK內部的預設引數設定參考直播場景精心校調過的。
step 1: 新增介面元素
為了能夠展示推流預覽的介面,您需要在您的佈局xml檔案里加入如下一段程式碼:
<com.tencent.rtmp.ui.TXCloudVideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:visibility ="gone"/>
step 2: 建立Pusher物件
先建立一個Pusher物件,它是所有SDK呼叫介面的承載者。
TXLivePusher mLivePusher = new TXLivePusher(getActivity());
step 3: 啟動推流
用下面這段程式碼就可以完成推流了:
String rtmpUrl = "rtmp://2157.livepush.myqcloud.com/live/xxxxxx";
mLivePusher.startPusher(rtmpUrl);
TXCloudVideoView mCaptureView = (TXCloudVideoView) view .findViewById(R.id.video_view);
mLivePusher.startCameraPreview(mCaptureView);
其中 startPusher 使用來告訴SDK視訊流要推到哪個伺服器地址去,而 startCameraPreview 則是將介面元素和Pusher物件關聯起來,從而能夠將手機攝像頭採集到的畫面渲染到螢幕上。
【小細節】
傳進來的self.view將會作為畫面渲染view的父view,建議此父view專門作為渲染使用,如果您想要在攝像頭畫面之上加彈幕、獻花之類的UI控制元件,請另行建立一個與self.view平級的view,並將該view疊加在self.view之上。
step 4: 美顏濾鏡
如果您是定位美女秀場,美顏是必不可少的一個功能點,本SDK提供了一種簡單版實現,包含磨皮(level 1 -> level 10)和美白 (level 1 -> level 3)兩個功能。您可以在您的APP得使用者操作介面上使用滑竿等控制元件來讓使用者選擇美顏效果,或者推薦您也可以先用Demo裡的滑竿進行,達到您滿意的效果後,將此時的數值固定到程式的設定引數裡。
介面函式setBeautyFilterDepth可以動態調整美顏及美白級別(不支援Neon指令優化的極個別Android手機無法開啟):
if (!mLivePusher.setBeautyFilter(mBeautyLevel, mWhiteningLevel)) {
Toast.makeText(getActivity().getApplicationContext(), "當前機型的效能無法支援美顏功能",
Toast.LENGTH_SHORT).show();
}
step 5: 控制攝像頭
- 切換前置或後置攝像頭 : 預設是使用前置攝像頭(可以通過修改config配置項來修改這個預設值),呼叫一次switchCamera 切換一次,注意切換攝像頭前必須保證 LivePushConfig 和 LivePush 物件都已經初始化。
// 預設是前置攝像頭
mLivePusher.switchCamera();
- 開啟或關閉閃光燈 : 只有後置攝像頭才可以開啟閃光燈,另外該介面需要在啟動預覽之後呼叫
//mFlashTurnOn為true表示開啟,否則表示關閉
if (!mLivePusher.turnOnFlashLight(mFlashTurnOn)) {
Toast.makeText(getActivity().getApplicationContext(),
"開啟閃光燈失敗:絕大部分手機不支援前置閃光燈!", Toast.LENGTH_SHORT).show();
}
- 攝像頭自動或手動對焦:大部分後置攝像頭才支援對焦,SDK支援2種對焦模式:手動對焦和自動對焦。自動對焦是系統提供的能力,但是跟機型相關,有些機型並不支援自動對焦。手動對焦和自動對焦是互斥的,開啟自動對焦後,手動對焦將不生效。SDK預設配置是手動對焦,您可以通過以下介面切換:
mLivePushConfig.setTouchFocus(mTouchFocus);
step 6: 設定Logo水印
這裡要特別說明一下,因為騰訊雲支援兩種方式設定水印:一種是在推流SDK進行設定,原理是在SDK內部進行視訊編碼前就給畫面打上水印。另一種方式是在雲端打水印,也就是雲端對視訊進行解析並新增水印Logo。
這裡我們特別建議您使用SDK新增水印
,因為在雲端打水印有三個明顯的問題:
(1)這是一種很耗機器的服務,會拉高您的費用成本;
(2)在雲端打水印對於推流期間切換解析度等情況的相容並不理想,會有很多花屏的問題發生。
(3)在雲端打水印會引入額外的3s以上的視訊延遲,這是轉碼服務所引入的。
SDK所要求的水印圖片格式為png,因為png這種圖片格式有透明度資訊,因而能夠更好地處理鋸齒等問題。(您可千萬別把jpg圖片在windows下改個字尾名就塞進去了,專業的png圖示都是需要由專業的美工設計師處理的)
//設定視訊水印
mLivePushConfig.setWatermark(BitmapFactory.decodeResource(getResources(),R.drawable.watermark), 10, 10);
mLivePusher.setConfig(mLivePushConfig);
step 7: 硬體編碼
通過PushConfig裡的setHardwareAcceleration介面可以開啟硬體編碼。
if (!HWSupportList.isHWVideoEncodeSupport()){
Toast.makeText(getActivity().getApplicationContext(),
"當前手機型號未加入白名單或API級別過低(最低16),請慎重開啟硬體編碼!",
Toast.LENGTH_SHORT).show();
}
mLivePushConfig.setHardwareAcceleration(mHWVideoEncode);
mLivePusher.setConfig(mLivePushConfig);
是否硬體編碼?
如果您的產品定位美女秀場,主用360*640這種解析度,硬體編碼並不比軟編碼好,因為Android的硬編碼不確定性比IOS要高很多,所以建議您不要開。
如果您的產品定位高清場景,540p或者720p高清推流,那就建議儘量開啟,因為Android手機的CPU降頻策略,以及核心多但每個核心電晶體都很少的現狀,註定了軟編碼處理540p都很吃力,所以硬體編碼才能把幀率撐到20幀以上。白名單策略
目前我們在Demo的HWSupportList.java檔案裡有一個白名單列表,這裡是我們自己團隊測試過的,可以放心開啟硬體加速的Android機型,後續時間裡我們會持續增加這個列表的機型數量。目前RTMP SDK測試團隊已經測試過的機型以及通過情況見 機型列表,供您參考。
定製篇
剛才講的是最基本的使用方法,能滿足絕大部分需求。
如果您是一位資深的軟體開發工程師,可能還有更專業的要求,比如您可能會關心SDK的執行狀態,或者會嘗試做一些視訊引數的定製等等,接下來我們看一下進階使用:
1. 如果您關心內部原理
首先,您需要了解一下視訊雲RTMP SDK的內部原理,在推流模式下,SDK內部的狀態機制如下:
簡單描述就是在您呼叫startPusher之後,RTMP SDK就會嘗試連線網路,並且啟動攝像頭和麥克風的音視訊採集,如果一切順利,就會進入推流主迴圈,之後如果一切正常,SDK內部會按照每秒一次的頻率通知當前的內部狀態(net status),如果中途出現什麼問題,則會以 event、 warning 或者 error 的形式通知出來。
2. 如果您關心狀態
想要獲得RTMP SDK的狀態通知,您可以提供一個Listener給剛才提到的Pusher物件,之後SDK的所有資訊都會通過這個Listener反饋給您的App.
public class MyTestActivity implements ITXLivePushListener{
@Override
public void onPushEvent(int event, Bundle param) {
// your code
}
public void onNetStatus(Bundle status) {
// your code
}
}
mLivePusher.setPushListener(this);
事件通知
- 常規事件 :一次成功的推流都會通知的事件,比如收到1003就意味著攝像頭的畫面會開始渲染了。
事件ID | 數值 | 含義說明 |
---|---|---|
PUSH_EVT_CONNECT_SUCC | 1001 | 已經成功連線到騰訊雲推流伺服器 |
PUSH_EVT_PUSH_BEGIN | 1002 | 與伺服器握手完畢,一切正常,準備開始推流 |
PUSH_EVT_OPEN_CAMERA_SUCC | 1003 | 推流器已成功開啟攝像頭(Android部分手機這個過程需要1-2秒) |
- 警告事件 :SDK發現了一些問題,比如主播的上行網路質量不理想,但並不意味著流程進行不下去。
事件ID | 數值 | 含義說明 |
---|---|---|
PUSH_WARNING_NET_BUSY | 1101 | 網路狀況不佳:上行頻寬太小,上傳資料受阻 |
PUSH_WARNING_RECONNECT | 1102 | 網路斷連, 已啟動自動重連 (自動重連連續失敗超過三次會放棄) |
PUSH_WARNING_HW_ACCELERATION_FAIL | 1103 | 硬編碼啟動失敗,採用軟編碼 |
PUSH_WARNING_DNS_FAIL | 3001 | RTMP -DNS解析失敗(會觸發重試流程) |
PUSH_WARNING_SEVER_CONN_FAIL | 3002 | RTMP伺服器連線失敗(會觸發重試流程) |
PUSH_WARNING_SHAKE_FAIL | 3003 | RTMP伺服器握手失敗(會觸發重試流程) |
- 錯誤通知 :SDK發現了一些嚴重問題,嚴重到推流是無法繼續的,比如使用者禁用了APP的Camera許可權導致攝像頭打不開。
事件ID | 數值 | 含義說明 |
---|---|---|
PUSH_ERR_OPEN_CAMERA_FAIL | -1301 | 開啟攝像頭失敗 |
PUSH_ERR_OPEN_MIC_FAIL | -1302 | 開啟麥克風失敗 |
PUSH_ERR_VIDEO_ENCODE_FAIL | -1303 | 視訊編碼失敗 |
PUSH_ERR_AUDIO_ENCODE_FAIL | -1304 | 音訊編碼失敗 |
PUSH_ERR_UNSUPPORTED_RESOLUTION | -1305 | 不支援的視訊解析度 |
PUSH_ERR_UNSUPPORTED_SAMPLERATE | -1306 | 不支援的音訊取樣率 |
PUSH_ERR_NET_DISCONNECT | -1307 | 網路斷連,且經三次搶救無效,可以放棄治療,更多重試請自行重啟推流 |
事件定義請參閱標頭檔案“TXLiveConstants.java”
網路狀態回撥
onNetStatus 通知每秒都會被觸發一次,目的是實時反饋當前的推流器狀態:
評估引數 | 含義說明 |
---|---|
NET_STATUS_VIDEO_BITRATE | 當前視訊編碼器輸出的位元率,也就是編碼器每秒生產了多少視訊資料,單位 kbps |
NET_STATUS_AUDIO_BITRATE | 當前音訊編碼器輸出的位元率,也就是編碼器每秒生產了多少音訊資料,單位 kbps |
NET_STATUS_VIDEO_FPS | 當前視訊幀率,也就是視訊編碼器每條生產了多少幀畫面 |
NET_STATUS_NET_SPEED | 當前的傳送速度 |
NET_STATUS_NET_JITTER | 網路抖動情況,抖動越大,網路越不穩定 |
NET_STATUS_CACHE_SIZE | 緩衝區大小,緩衝區越大,說明當前上行頻寬不足以消費掉已經生產的視訊資料 |
3. 如果您關心引數
如果您希望定製視訊編碼引數,音訊編碼引數等等,您可以通過設定Config物件實現您的自定義需求,目前我們支援的setting介面如下:
引數名 | 含義 | 預設值 |
---|---|---|
setAudioSampleRate | 音訊取樣率:錄音裝置在一秒鐘內對聲音訊號的採集次數 | 44100 |
setANS | 噪聲抑制:開啟後可以濾掉背景雜音(32000以下采樣率有效) | 關閉 |
setVideoFPS | 視訊幀率:即視訊編碼器每秒生產出多少幀畫面,注意由於大部分安卓機器攝像頭不支援30FPS以上的採集,推薦您設定FPS為20 | 20 |
setVideoResolution | 視訊解析度:目前提供三種16:9解析度可供您選擇 | 640 * 360 |
setVideoBitrate | 視訊位元率:即視訊編碼器每秒生產出多少資料,單位 kbps | 800 |
setVideoEncodeGop | 關鍵幀間隔(單位:秒)即多少秒出一個I幀 | 5s |
setAutoAdjustBitrate | 頻寬自適應:該功能會根據當前網路情況,自動調整視訊位元率,避免視訊資料超出傳送能力而導致畫面卡頓 | 開 |
setMaxVideoBitrate | 最大輸出位元速率:只有開啟自適應位元速率, 該設定項才能啟作用 | 1200 |
setMinVideoBitrate | 最小輸出位元速率:只有開啟自適應位元速率, 該設定項才能啟作用 | 800 |
setWatermark | 設定水印圖片以及其相對螢幕左上角位置 | 騰訊雲Logo(demo) |
setHomeOrientation | 設定視訊影象旋轉角度,比如是否要橫屏推流 | home在右邊(0)home在下面(1)home在左面(2)home在上面(3) |
這些引數的設定推薦您在啟動推流之前就指定,因為大部分設定項是在重新推流之後才能生效的。參考程式碼如下:
// 成員變數中宣告 config 和 pusher
private TXLivePushConfig mLivePushConfig = new TXLivePushConfig();
private TXLivePusher mLivePusher = new TXLivePusher(getActivity());
// 修改引數設定
mLivePushConfig.setVideoResolution(TXLiveConstants.VIDEO_RESOLUTION_640_360);
mLivePushConfig.setAutoAdjustBitrate(false);
mLivePushConfig.setVideoBitrate(800);
//設定視訊水印
mLivePushConfig.setWatermark(BitmapFactory.decodeResource(
getResources(),R.drawable.watermark), 10, 10);
mLivePusher.setConfig(mLivePushConfig);
4. 如果您想自己加工視訊資料
有些研發能力比較強的客戶,會有自定義影象處理的需求(比如自定義影象濾鏡),同時又希望複用rtmp sdk的整體流程,如果是這樣,您可以按照如下攻略進行定製。
-
Step1. 實現一個影象處理的so
您需要自己實現一個so,比如test.so,然後按照如下定義匯出一個C風格的函式,之所以強制使用C而不是java是因為影象處理的效率C和C++比較容易勝任。您實現的PVideoProcessHookFunc 處理時間不能過長,試想,如果該函式的處理時間超過50ms,那就意味著SDK推出的視訊流,其幀率不可能達到20FPS。/* @brief 客戶自定義的視訊預處理函式原型 * @param yuv_buffer:視訊YUV資料,格式固定是YUV420 Planar * @param len_buffer:資料長度 * @param width: 視訊width * @param height: 視訊height * @return * @remark (1)該函式會被SDK同步呼叫,故您需要同步返回預處理後的資料 * (2)處理後的資料長度必須和處理前保持一致 * (3)您或者直接處理yuv_buffer,或者將處理後的資料memcpy到yuv_buffer所指的記憶體區域, * 這塊記憶體的生命期由SDK負責管理(也就是釋放) */ typedef void (*PVideoProcessHookFunc)(unsigned char * yuv_buffer, int len_buffer, int width, int height);
-
Step2. 設定 setCustomModeType + setCustomVideoPreProcessLibrary
它們是位於PushConfig中的兩個設定項:
setCustomModeType設定項用來宣告“您希望自定義濾鏡”,setCustomVideoPreProcessLibrary用來指定您自己的so的檔案路徑以及匯出函式的名字。//libtest.so是step1中您自己用C/C++實現的影象處理函式庫 //MyVideoProcessFunc是該so匯出的函式的名字,該函式必須符合step1中的PVideoProcessHookFunc定義 int customMode |= TXLiveConstants.CUSTOM_MODE_VIDEO_PREPROCESS; String path = this.getActivity().getApplicationInfo().dataDir + "/lib"; mLivePushConfig.setCustomModeType(customMode); mLivePushConfig.setCustomVideoPreProcessLibrary(path +"/libtest.so", "MyVideoProcessFunc");
5. 如果您想自己加工音訊資料
類似視訊資料處理思路,但是具體的函式和引數名稱要換成音訊相關的,java層示例程式碼如下:
customMode |= TXLiveConstants.CUSTOM_MODE_AUDIO_PREPROCESS; //可以和VIDEO_PREPROCESS一起設定
String path = this.getActivity().getApplicationInfo().dataDir + "/lib";
mLivePushConfig.setCustomModeType(customMode);
mLivePushConfig.setCustomAudioPreProcessLibrary(path +"/libtest.so", "MyAudioProcessFunc");
其中MyAudioProcessFunc應當遵循如下的函式宣告:
/* @brief 客戶自定義的音訊預處理函式原型
* @param pcm_buffer: 音訊PCM資料
* @param len_buffer: 資料長度
* @param sample_rate: 取樣頻率
* @param channels: 聲道數
* @param bit_size: 取樣位寬
* @return
* @remark (1)該函式會被SDK同步呼叫,故您需要同步返回預處理後的資料
* (2)處理後的資料長度必須和處理前保持一致
* (3)您或者直接處理pcm_buffer,或者將處理後的資料memcpy到pcm_buffer所指的記憶體區域,
* 這塊記憶體的生命期由SDK負責管理(也就是釋放)
*/
typedef void (*PAudioProcessHookFunc)(unsigned char * pcm_buffer, int len_buffer,
int sample_rate, int channels, int bit_size);
6. 如果您只用SDK來推流
也有客戶只是希望拿SDK用來推流,音視訊採集部分由自己的程式碼控制,SDK用來做音視訊編碼和推流就可以了。
如果是這樣,您可以按如下步驟實現:
-
Step1. 設定 setCustomModeType 和相關引數
這裡需要將CustomMode設定為CUSTOM_MODE_VIDEO_CAPTURE,含義是“不需要SDK採集音視訊資料”,同時還需要設定視訊解析度。// (1)將 CustomMode 設定為:自己採集視訊資料,SDK只負責編碼傳送 int customMode |= TXLiveConstants.CUSTOM_MODE_VIDEO_CAPTURE; mLivePushConfig.setCustomModeType(customMode); // // (2)設定視訊編碼引數,比如720p,相比如普通模式,VIDEO_CAPTURE模式您有六種解析度可供選擇 // VIDEO_RESOLUTION_TYPE_360_640: pYUVBuff的解析度必須符合360*640 // VIDEO_RESOLUTION_TYPE_540_960: pYUVBuff的解析度必須符合540*960 // VIDEO_RESOLUTION_TYPE_720_1280: pYUVBuff的解析度必須符合720*1280 // VIDEO_RESOLUTION_TYPE_640_360: pYUVBuff的解析度必須符合640*360 // VIDEO_RESOLUTION_TYPE_960_540: pYUVBuff的解析度必須符合960*540 // VIDEO_RESOLUTION_TYPE_1280_720: pYUVBuff的解析度必須符合1280*720 mLivePushConfig.setVideoResolution(TXLiveConstants.VIDEO_RESOLUTION_TYPE_1280_720);
-
Step2. 使用 sendCustomYUVData 向SDK填充資料
之後的工作就是向SDK塞入您自己準備好的視訊資料(YUV420 Planar),剩下的編碼和網路傳送等工作交給SDK來解決。//(1)先啟動推流,不然您給SDK資料它也不會處理 mLivePusher.startPusher(rtmpUrl.trim()); // //(2)此處示例程式碼我們用一個執行緒來模擬YUV資料傳送 new Thread() { @Override public void run() { while (true) { try { FileInputStream in = new FileInputStream("/sdcard/test_1280_720.yuv"); int len = 1280 * 720 * 3 / 2; //yuv格式為i420 byte buffer[] = new byte[len]; int count = 0; while ((count = in.read(buffer)) != -1) { if (len == count) { mLivePusher.sendCustomYUVData(buffer); } else { break; } sleep(50, 0); } in.close(); }catch (Exception e) { e.printStackTrace(); } } } }.start();
- Step3. 音訊也是一樣的
音訊也是同樣的處理思路,只是使用對應的 CustomMode 應當設定為 CUSTOM_MODE_AUDIO_CAPTURE,於此同時,您也需要指定聲音取樣率等和聲道數等關鍵資訊。
之後,呼叫sendCustomPCMData向SDK塞入您自己的PCM資料即可。// (1)將 CustomMode 設定為:自己採集音訊資料,SDK只負責編碼&傳送 int customMode |= TXLiveConstants.CUSTOM_MODE_AUDIO_PREPROCESS; mLivePushConfig.setCustomModeType(customMode); // // (2)設定音訊編碼引數:音訊取樣率和聲道數 mLivePushConfig.setAudioSampleRate(44100); mLivePushConfig.setAudioChannels(1);