天天看點

Android 雲遊戲實作

       公司最近有和雲遊戲相關的業務,最開始采用的是virtualdisplay +mediacodec實作,螢幕視訊錄制編碼推流。但是mediacodec編碼有很多參數設定不了,而且雲主機的cpu性能完完全全高于GPU 是以,就準備采用軟體編碼實作。基于X264+minicap實作也可以了解為把bitmap轉為H264視訊通過RTMP傳輸。

先上流程圖:

Android 雲遊戲實作

1 minicap :是一個高速的截圖工具,具體如何安裝使用可以檢視github上的流程

2 資料解析:minicap提供了一個localsocket往外吐資料我們可以在Android端解析該資料,關鍵代碼如下

private void getMinicapData() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    mMinicapClientSocket.connect(mAddr);
                    InputStream inputStream = mMinicapClientSocket.getInputStream();

                    long start = System.currentTimeMillis();
                    while (isLiving) {
                        byte[] buffer = new byte[FRAME_SIZE];
                        int realLen = inputStream.read(buffer);
                        if (buffer.length != realLen) {
                            buffer = subByteArray(buffer, 0, realLen);
                        }

                        int len = buffer.length;
                        for (int cursor = 0; cursor < len; ) {
                            int byte10 = buffer[cursor] & 0xff;
                            if (readBannerBytes < bannerLength) {
                                cursor = parserBanner(cursor, byte10);
                            } else if (readFrameBytes < 4) {
                                // 第二次的緩沖區中前4位數字和為frame的緩沖區大小
                                frameBodyLength += (byte10 << (readFrameBytes * 8)) >>> 0;
                                cursor += 1;
                                readFrameBytes += 1;
                                Log.i(TAG, "解析圖檔大小 = " + readFrameBytes);
                            } else {
                                if (len - cursor >= frameBodyLength) {
                                    Log.i(TAG, "frameBodyLength = " + frameBodyLength);
                                    byte[] subByte = subByteArray(buffer, cursor,
                                            cursor + frameBodyLength);
                                    frameBody = byteMerger(frameBody, subByte);
                                    if ((frameBody[0] != -1) || frameBody[1] != -40) {
                                        Log.i(TAG, String.format("Frame body does not start with JPG header"));
                                        return;
                                    }
                                    final byte[] finalBytes = subByteArray(frameBody, 0, frameBody.length);
                                    // 轉化成bitmap
                                    mBitmap = BitmapFactory.decodeByteArray(finalBytes, 0, finalBytes.length);
                                    // 這裡開始推送
                                    mLivePusher.native_push_video(miniCapRGBChange.getYUVByBitmap(mBitmap));

                                    long current = System.currentTimeMillis();
                                    Log.i(TAG, "圖檔已生成,耗時: "
                                            + TimeUtil.formatElapsedTime(current
                                            - start));
                                    start = current;
                                    cursor += frameBodyLength;
                                    restore();
                                } else {
                                    Log.i(TAG, "所需資料大小 : " + frameBodyLength);
                                    byte[] subByte = subByteArray(buffer, cursor, len);
                                    frameBody = byteMerger(frameBody, subByte);
                                    frameBodyLength -= (len - cursor);
                                    readFrameBytes += (len - cursor);
                                    cursor = len;
                                }
                            }
                        }
                    }

                } catch (Exception e) {
                    Log.i(TAG, String.format(" get mini data Exception"+e));
                }

            }


        }).start();


    }

           

3 .android 移植x264

  x264是一個免費的開源庫,可以移植到Android上來,具體可檢視官網或者網上搜尋如何移植。

 編碼參數關鍵代碼

void VideoChannel::setVideoEncodeInfo(int width, int height, int fps, int bitrate) {

    // 編碼參數設定可以參考
    //https://www.cnblogs.com/wainiwann/p/5647521.html
    pthread_mutex_lock(&mutex);

    mWidth = width;
    mHeight = height;

    mFps = fps;
    mBitrate = bitrate;

    ySize = width * height;
    uvSize = ySize / 4;

    if (videoCodec) {
        x264_encoder_close(videoCodec);
        videoCodec = 0;
    }
    if (pic_in) {
        x264_picture_clean(pic_in);
        delete pic_in;
        pic_in = 0;
    }

    //打開x264解碼器
    //x264解碼器的屬性
    x264_param_t param;
    //ultrafast 最快
    //zerolatency 無延遲解碼
    x264_param_default_preset(&param, "ultrafast", "zerolatency");
    param.i_level_idc = 30;
    //輸入資料格式 int csp=X264_CSP_BGR|X264_CSP_VFLIP;    //這個格式是BITMAP的那種颠倒的BGR的格式
    param.i_csp = X264_CSP_I420;
    param.i_width = width;
    param.i_height = height;

    //無b幀
    param.i_bframe = 0;
    //參數i_rc_method表示碼率控制, CQP(恒定品質) CRF(恒定碼率) ABR(平均碼率)
    param.rc.i_rc_method = X264_RC_CRF;
    //碼率(比特率 機關Kbps)
    param.rc.i_bitrate = bitrate / 1000;


    LOGI("set_i_bitrate------------------>%d",bitrate/1000);
    //瞬時最大碼率
    param.rc.i_vbv_max_bitrate = bitrate / 1000 * 1.2;
    //設定了i_vbv_max_bitrate必須設定此參數, 碼率控制區大小 機關kbps
    param.rc.i_vbv_buffer_size = bitrate / 1000;

    //幀率
    param.i_fps_num = fps;
    param.i_fps_den = 1;
    param.i_timebase_den = param.i_fps_num;
    param.i_timebase_num = param.i_fps_den;

    //用fps而不是時間戳來計算幀間距離
    param.b_vfr_input = 0;
    //幀距離(關鍵幀) 2s一個關鍵幀
    param.i_keyint_max = fps * 2;

    //是否指派sps和pps放在每個關鍵幀的前面 該參數設定是讓每個關鍵幀(I幀)都附帶sps/pps
    param.b_repeat_headers = 1;
    //多線程
    param.i_threads = 1;
    x264_param_apply_profile(&param, "baseline");
//    x264_param_apply_profile(&param, "high");
    //打開解碼器
    videoCodec = x264_encoder_open(&param);
    pic_in = new x264_picture_t;

    x264_picture_alloc(pic_in, X264_CSP_I420, width, height);
    pthread_mutex_unlock(&mutex);
}           

4 rtmp移植 :網上教程比較多可自行參考。

5最後效果圖:

Android 雲遊戲實作

時間來不及就先看看圖檔吧。

老版本的螢幕錄制可以看看上一篇文章,新版的螢幕錄制需要搭好minicap環境,有些9.0手機可能不能允許minicap,如果是手機的話還是建議采用上一篇文章的方案,如果是自己要做這方面的業務可以采用新版本。 新版本的可以等我後面放到github上。