1. 程式人生 > >安卓直播的推流和播放

安卓直播的推流和播放

為啥想寫這篇部落格呢,也是因為自己這一週開發走過不少坎坷路途,希望對有同樣需求的同志們有些許幫助。說說最近的專案,需要遠端控制一個硬體裝置,通過網際網路傳送指令,後來硬是加上遠端監控,所以就有了接下來的糾結。需要在遠端控制端加上個播放流媒體的功能,執行端需要呼叫攝像頭進行推流。我也是調研很多,以前也沒做過相關的,最終選擇使用RTMP協議推流,nginx伺服器轉發流媒體,遠端採用Vitamio播放RTMP流。

實現方法

推流端publisher
執行端要加上推流,找了很久最終選擇github上一位大神分享的免費RTMP推流SDK,android_rtmppush_sdk下載地址,但是我遇到個問題,我需要的是橫屏推流和小視窗嵌入介面,仔細看了他附帶demo中的程式碼終於找到方法。我們知道從攝像頭提取的資料先得轉換成yuv格式(yuv420 = _swEncH264.swapNV21toI420(YUV, W, H)根據手機ImageFormat設定NV21或YV12來選擇swap函式),然後裁剪(demo中使用獲取相機預設的尺寸,然後設定成推流的寬高比例),相機返回尺寸預設是寬度大於高度的,作者為了實現豎屏推流將寬設定小於高,但是在處理的時候都是將引數倒過來用,之後還將yuv資料旋轉270度(後置攝像頭需要旋轉90度)。sdk中集成了yuv資料的90度和270度旋轉的方法(SWVideoEncoder類中YUV420pRotate90、YUV420pRotate270方法),當然也可以自己寫,但是效率啥的這裡應該更好。
回到我的專案,我需要的是前置攝像頭橫屏推流,這裡在xml佈局檔案中加了個SurfaceView,設定大小為推流視訊解析度尺寸或者等比放大。橫屏推流就不需要旋轉yuv資料角度,直接賦值_yuvEdit = yuvData就好,根據需求旋轉90或270度(試過這sdk中只有這兩種旋轉方式),例如_yuvEdit = _swEncH264.YUV420pRotate90(yuvData, W,H) (W>H預設相機獲取圖形解析度寬大於高)。在進行編碼時候的寬高是傳送推流後顯示的寬高_swEncH264 = new SWVideoEncoder(W, H, F, B)。
倘若需要橫豎屏推流切換的話,應該在編碼前進行手機重力感應進行方向判斷(demo中有getWindowManager().getDefaultDisplay().getRotation()函式返回螢幕方向,但遇到某些定製平板橫豎底層調轉了,要自己除錯一下,或者手動設定角度),然後相應旋轉yuv資料,編碼輸出相應尺寸。
呼叫前置攝像頭經常需要映象處理,發一段yuv資料映象轉換的函式

public void Mirror(byte[] src, int w, int h) {//src是原始yuv陣列
        int i;
        int index;
        byte temp;
        int a, b;
        //mirror y
        for (i = 0; i < h; i++) {
            a = i * w;
            b = (i + 1) * w - 1;
            while (a < b) {
                temp = src[a];
                src[a] = src[b];
                src[b] = temp;
                a++;
                b--;
            }
        }
        //mirror u
index = w * h;//U起始位置 for (i = 0; i < h / 2; i++) { a = i * w / 2; b = (i + 1) * w / 2 - 1; while (a < b) { temp = src[a + index]; src[a + index] = src[b + index]; src[b + index] = temp; a++; b--; } } //mirror v
index = w * h / 4 * 5;//V起始位置 for (i = 0; i < h / 2; i++) { a = i * w / 2; b = (i + 1) * w / 2 - 1; while (a < b) { temp = src[a + index]; src[a + index] = src[b + index]; src[b + index] = temp; a++; b--; } } }

服務端nginx
服務端採用的是nginx作為流媒體伺服器,下載的是帶rtmp模組的伺服器,nginx-rtmp-win32下載地址,監聽埠(網路埠、RTMP埠)可以在/conf/nginx.conf裡面修改,開啟伺服器後,可以在http://ip:8080/index.html訪問檢視連線的視訊流影像,在http://ip:8080/stat 訪問連線裝置狀態(其中ip本地可以選擇localhost,遠端訪問需要公網ip)。
播放端player
遠端控制端進行直播拉流播放,ijkplayer挺好用,可是我這邊總是編譯不了,最後選擇vitamio外掛,VitamioSDK下載地址 ,怎麼加入它的sdk就不說了,說說怎麼呼叫,佈局檔案中加入

<io.vov.vitamio.widget.VideoView
     android:id="@+id/vv"
     android:layout_width="640px"
     android:layout_height="480px"
     android:layout_centerHorizontal="true" />

在清單檔案中加入
<activity android:name="io.vov.vitamio.activity.InitActivity" />
不宣告沒法使用播放器,注意之後使用的videoview類是import vitamio外掛裡面的類,安卓自帶的那個不能播放rtmp流。
在activity中加入

if (Vitamio.isInitialized(this)) {
    mVideoView = (VideoView) findViewById(R.id.vv);
    mVideoView.setVideoPath(url_path);
    mVideoView.setBufferSize(512);
    mVideoView.requestFocus();
    mVideoView.start();
    mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener(){
        @Override
        public void onPrepared(MediaPlayer mediaPlayer) {
            mediaPlayer.setPlaybackSpeed(1.0f);
        }
    });
}

我這裡使用的只是基本功能,比如控制條、緩衝等待顯示、亮度調節、聲音調節等等功能,都能在官方文件中找到。
RTMP延遲
RTMP流的播放延遲也是讓我抓狂,分享一點思路吧。從兩個方面:推流端,sdk中在Thread _h264EncoderThread中有個for迴圈,迴圈500次每次Thread.sleep(10),總共5000(5秒)的固定緩衝池蓄積時間,然後再取資料傳送給伺服器,這在弱網環境下比較有效果,直播顯得連貫,但是在網路較好的情況下這五秒是無法逾越的延遲,可以適當調小一些,我發的視訊流解析度也不高,調到2.5秒在wifi下推流發現絲毫不影響;Vitamio啟動播放也耗時不少,因為我需要的是遠端點播,可以在程式啟動時早早例項化videoview,並且把緩衝區設定小一些,之後再呼叫start()方法,網速好的話延遲能控制在3秒以內,弱網丟包重傳會很慢,累積延遲,可以設定超時強制重連重新整理。
特別感謝
最後感謝CSDN部落格的博主huaxun66,正是參考他的方法才能實現我的功能,也要感謝android_rtmppush_sdk作者Alex.CR,還有Vitamio團隊的播放器。

參考