1. 程式人生 > >騰訊直播——推流SDK(Android)

騰訊直播——推流SDK(Android)

功能篇

騰訊視訊雲RTMP SDK由兩部分構成:推流器 + 播放器,本文將主要介紹推流器的相關資訊。

該SDK遵循標準RTMP視訊推送協議,可以對接包括騰訊雲在內的標準視訊直播伺服器。與此同時,SDK內部囊括了騰訊音視訊團隊多年的技術積累,在視訊壓縮、硬體加速、美顏濾鏡、音訊降噪、位元速率控制等方面都做了很多的優化處理。

如果您是一位剛剛接觸視訊直播的合作伙伴,您只需要幾行程式碼就可以完成對接流程,而如果您是一位資深的移動端軟體開發工程師,SDK所提供的豐富的設定介面,亦可讓您能夠定製出最符合需求的表現。

rtmp sdk push

SDK開發包附帶的推流器DEMO介面如下:

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內部的狀態機制如下:

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,於此同時,您也需要指定聲音取樣率等和聲道數等關鍵資訊。
    // (1)將 CustomMode 設定為:自己採集音訊資料,SDK只負責編碼&傳送
    int customMode |= TXLiveConstants.CUSTOM_MODE_AUDIO_PREPROCESS; 
    mLivePushConfig.setCustomModeType(customMode);
    //
    // (2)設定音訊編碼引數:音訊取樣率和聲道數
    mLivePushConfig.setAudioSampleRate(44100); 
    mLivePushConfig.setAudioChannels(1);
    
    之後,呼叫sendCustomPCMData向SDK塞入您自己的PCM資料即可。