Android 云游戏实现

       公司最近有和云游戏相关的业务,最开始采用的是virtualdisplay +mediacodec实现,屏幕视频录制编码推流。但是mediacodec编码有很多参数设置不了,而且云主机的cpu性能完完全全高于GPU 所以,就准备采用软件编码实现。基于X264+minicap实现也可以理解为把bitmap转为H264视频通过RTMP传输。

先看效果图:

云手机同步效果

先上流程图:

Android 云游戏实现_第1张图片

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(¶m, "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(¶m, "baseline");
//    x264_param_apply_profile(¶m, "high");
    //打开解码器
    videoCodec = x264_encoder_open(¶m);
    pic_in = new x264_picture_t;

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

4 rtmp移植 :网上教程比较多可自行参考。

5最后效果图:

Android 云游戏实现_第2张图片

时间来不及就先看看图片吧。

老版本的屏幕录制可以看看上一篇文章,新版的屏幕录制需要搭好minicap环境,有些9.0手机可能不能允许minicap,如果是手机的话还是建议采用上一篇文章的方案,如果是自己要做这方面的业务可以采用新版本。 新版本的可以等我后面放到github上。

优化后在Android 和网页上的效果如下:

   

你可能感兴趣的:(C/C++,Android,云游戏)