乐趣区

关于云计算:GameAnywhere代码结构

GameAnywhere 是一个开源的云游戏平台,由 Chun-Ying Huang 在 2013 年开发,最开始是毕业论文钻研应用,近期随着云游戏风口正盛,这个我的项目关注度又有起色,本文介绍 GameAnywhere 代码形成。GameAnywhere 的 License 为 BSD3,能够批改代码后闭源,然而我的项目依赖的开源组件较多,须要留神 License。

代码形成

目录构造

├─bin
│  ├─config
│  │  └─common
│  ├─data
│  ├─log
│  └─mod
├─deps.pkg.win32      #依赖组件的 windows 包
│  ├─bin
│  ├─NvCodec
│  └─NVENC
├─deps.posix          #依赖组件的 unix 包
│  └─lib
├─deps.src            #依赖组件的源码
│  └─patches
├─deps.win32          #依赖组件的 Windows 头文件与库
│  ├─bin
│  ├─include
│  │  ├─include
│  │  ├─lib
│  │  ├─libavcodec
│  │  ├─libavdevice
│  │  ├─libavfilter
│  │  ├─libavformat
│  │  ├─libavutil
│  │  ├─libpostproc
│  │  ├─libswresample
│  │  ├─libswscale
│  │  ├─live555
│  │  ├─NVENC
│  │  └─SDL2
│  └─lib
├─docs
└─ga
    ├─android   # android 客户端
    ├─client    # windows 客户端
    ├─core      # 公共依赖局部
    ├─module    # 各个功能模块
    │  ├─asource-system   # apple 平台的音频抓取
    │  ├─ctrl-sdl         # 键盘输入
    │  ├─encoder-audio    # 音频编码
    │  ├─encoder-mfx
    │  ├─encoder-nvenc    # NVIDIA 硬编码
    │  ├─encoder-video    # FFmpeg 软编码
    │  ├─encoder-x264     # 编码 x264 库
    │  ├─filter-rgb2yuv   # rgb 转 yuv 库,编码前预处理
    │  ├─server-ffmpeg    # FFmpeg 库
    │  ├─server-live555   # live555 我的项目,承载 rtsp 协定
    │  └─vsource-desktop  # DirectX GDI 抓图
    ├─server
    │  ├─event-driven     # 键盘鼠标输出 windows 平台实现
    │  ├─event-posix      # 键盘鼠标输出 posix 平台实现
    │  └─periodic         # 可执行文件 Main 函数,依赖 ga/core
    └─vs2010    #Visual Studio 工程
        ├─BasicUsageEnvironment
        ├─CloudGameDM_Installer
        ├─encoder-nvenc
        ├─ga-client
        ├─ga-hook
        ├─ga-server-event-driven
        ├─ga-server-periodic
        ├─groupsock
        ├─ipch
        ├─libga
        ├─liveMedia
        ├─module-asource-system
        ├─module-ctrl-sdl
        ├─module-encoder-audio
        ├─module-encoder-video
        ├─module-encoder-x264
        ├─module-filter-rgb2yuv
        ├─module-server-ffmpeg
        ├─module-server-live555
        ├─module-vsource-desktop
        ├─module-vsource-desktop-d3d
        ├─module-vsource-desktop-dfm
        └─UsageEnvironment

服务端

Main: server\ga\server\periodic\ga-server-periodic.cpp

Server 端的性能:图像采集、图像预处理、声音采集、键盘输入、音视频编码、封包、传输性能,这些性能别离由对应 GameAnywhere 定义的模块实现。

//
if (load_modules() < 0) {write_log("error, load_modules error ! \n");
  return -1;
}
if (init_modules() < 0) {write_log("error, init_modules error ! \n");
  return -1;
}
if (run_modules() < 0) {write_log("error, run_modules error ! \n");
  return -1;
}

GameAnywhere 模块类型定义: server\ga\core\ga-module.h

/**
 * Enumeration for types of a module.
 */
enum ga_module_types {
    GA_MODULE_TYPE_NULL = 0,    /**< Not used */
    GA_MODULE_TYPE_CONTROL,        /**< Is a controller module */
    GA_MODULE_TYPE_ASOURCE,        /**< Is an audio source module */
    GA_MODULE_TYPE_VSOURCE,        /**< Is an video source module */
    GA_MODULE_TYPE_FILTER,        /**< Is a filter module */
    GA_MODULE_TYPE_AENCODER,    /**< Is an audio encoder module */
    GA_MODULE_TYPE_VENCODER,    /**< Is a video encoder module */
    GA_MODULE_TYPE_ADECODER,    /**< Is an audio decoder module */
    GA_MODULE_TYPE_VDECODER,    /**< Is a video decoder module */
    GA_MODULE_TYPE_SERVER        /**< Is a server module */
};

GameAnywhere 模块定义: server\ga\core\ga-module.h

/**
 * Data strucure to represent a module.
 */
typedef struct ga_module_s {
    HMODULE    handle;        /**< Handle to a module */
    int type;        /**< Type of the module */
    char *name;        /**< Name of the module */
    char *mimetype;        /**< MIME-type of the module */
    int (*init)(void *arg);        /**< Pointer to the init function */
    int (*start)(void *arg);    /**< Pointer to the start function */
    //void * (*threadproc)(void *arg);
    int (*stop)(void *arg);        /**< Pointer to the stop function */
    int (*deinit)(void *arg);    /**< Pointer to the deinit function */
    int (*ioctl)(int command, int argsize, void *arg);    /**< Pointer to ioctl function */
    int (*notify)(void *arg);    /**< Pointer to the notify function */
    void * (*raw)(void *arg, int *size);    /**< Pointer to the raw function */
    int (*send_packet)(const char *prefix, int channelId, AVPacket *pkt, int64_t encoderPts, struct timeval *ptv);    /**< Pointer to the send packet function: sink only */
    void * privdata;        /**< Private data of this module */
}    ga_module_t;

模块生命周期动作: server\ga\core\ga-module.h

EXPORT ga_module_t * ga_load_module(const char *modname, const char *prefix);
EXPORT void ga_unload_module(ga_module_t *m);
EXPORT int ga_init_single_module(const char *name, ga_module_t *m, void *arg);
EXPORT void ga_init_single_module_or_quit(const char *name, ga_module_t *m, void *arg);
EXPORT int ga_run_single_module(const char *name, void * (*threadproc)(void*), void *arg);
EXPORT void ga_run_single_module_or_quit(const char *name, void * (*threadproc)(void*), void *arg);
// module function wrappers
EXPORT int ga_module_init(ga_module_t *m, void *arg);
EXPORT int ga_module_start(ga_module_t *m, void *arg);
EXPORT int ga_module_stop(ga_module_t *m, void *arg);
EXPORT int ga_module_deinit(ga_module_t *m, void *arg);
EXPORT int ga_module_ioctl(ga_module_t *m, int command, int argsize, void *arg);
EXPORT int ga_module_notify(ga_module_t *m, void *arg);
EXPORT void * ga_module_raw(ga_module_t *m, void *arg, int *size);
EXPORT int ga_module_send_packet(ga_module_t *m, const char *prefix, int channelId, AVPacket *pkt, int64_t encoderPts, struct timeval *ptv);

图像采集模块: server\ga\module\vsource-desktop

此模块提供 桌面级别 图像采集性能,未提供 过程级别 图像采集。提供 DirectXGDI两种图像采集实现。

ga_module_t *
module_load() {
    static ga_module_t m;
    bzero(&m, sizeof(m));
    m.type = GA_MODULE_TYPE_VSOURCE;
    m.name = strdup("vsource-desktop");
    m.init = vsource_init;
    m.start = vsource_start;
    m.stop = vsource_stop;
    m.deinit = vsource_deinit;
    m.ioctl = vsource_ioctl;
    return &m;
}

图像预处理模块: server\ga\module\filter-rgb2yuv\filter-rgb2yuv.cpp

此模块提供将抓取到的 RGB 格局图片转换为 YUV 格局。

ga_module_t *
module_load() {
    static ga_module_t m;
    bzero(&m, sizeof(m));
    m.type = GA_MODULE_TYPE_FILTER;
    m.name = strdup("filter-RGB2YUV");
    m.init = filter_RGB2YUV_init;
    m.start = filter_RGB2YUV_start;
    m.stop = filter_RGB2YUV_stop;
    m.deinit = filter_RGB2YUV_deinit;
    //m.threadproc = filter_RGB2YUV_threadproc;
    return &m;
}

NVIDIA 硬编码模块: server\ga\module\encoder-nvenc\encoder-nvenc.cpp

ga_module_t *
module_load() {
    static ga_module_t m;
    struct RTSPConf *rtspconf = rtspconf_global();
    //
    bzero(&m, sizeof(m));
    m.type = GA_MODULE_TYPE_VENCODER;
    m.name = strdup("nvenc-video-encoder");
    m.mimetype = strdup("video/H264");
    m.init  = nvenc_init;
    m.deinit= nvenc_deinit;
    m.start = nvenc_start;
    m.ioctl = nvenc_ioctl;
    m.stop  = nvenc_stop;

    return &m;
}

软编码模块(ffmpge 实现): server\ga\module\encoder-video\encoder-video.cpp

ga_module_t *
module_load() {
    static ga_module_t m;
    //struct RTSPConf *rtspconf = rtspconf_global();
    char mime[64];
    //
    bzero(&m, sizeof(m));
    m.type = GA_MODULE_TYPE_VENCODER;
    m.name = strdup("ffmpeg-video-encoder");
    if(ga_conf_readv("video-mimetype", mime, sizeof(mime)) != NULL) {m.mimetype = strdup(mime);
    }
    m.init = vencoder_init;
    m.start = vencoder_start;
    //m.threadproc = vencoder_threadproc;
    m.stop = vencoder_stop;
    m.deinit = vencoder_deinit;
    //
    m.raw = vencoder_raw;
    m.ioctl = vencoder_ioctl;
    return &m;
}

软编码模块(x264 实现): server\ga\module\encoder-x264\encoder-x264.cpp

ga_module_t *
module_load() {
    static ga_module_t m;
    //
    bzero(&m, sizeof(m));
    m.type = GA_MODULE_TYPE_VENCODER;
    m.name = strdup("x264-video-encoder");
    m.mimetype = strdup("video/H264");
    m.init = vencoder_init;
    m.start = vencoder_start;
    //m.threadproc = vencoder_threadproc;
    m.stop = vencoder_stop;
    m.deinit = vencoder_deinit;
    //
    m.raw = vencoder_raw;
    m.ioctl = vencoder_ioctl;
    return &m;
}

音频采集模块: server\ga\module\asource-system\asource-system.cpp

在 windows 操作系统上,应用微软的 IAudioClient API 采集声音。

ga_module_t *
module_load() {
    static ga_module_t m;
    bzero(&m, sizeof(m));
    m.type = GA_MODULE_TYPE_ASOURCE;
    m.name = strdup("asource-system");
    m.init = asource_init;
    m.start = asource_start;
    //m.threadproc = asource_threadproc;
    m.stop = asource_stop;
    m.deinit = asource_deinit;
    return &m;
}

音频编码模块: server\ga\module\encoder-audio\encoder-audio.cpp

应用 ffmpeg 封装的 API 编码音频,编码格局反对 LAME/OPUS。

ga_module_t *
module_load() {
    static ga_module_t m;
    struct RTSPConf *rtspconf = rtspconf_global();
    char mime[64];
    bzero(&m, sizeof(m));
    m.type = GA_MODULE_TYPE_AENCODER;
    m.name = strdup("ffmpeg-audio-encoder");
    if(ga_conf_readv("audio-mimetype", mime, sizeof(mime)) != NULL) {m.mimetype = strdup(mime);
    }
    m.init = aencoder_init;
    m.start = aencoder_start;
    //m.threadproc = aencoder_threadproc;
    m.stop = aencoder_stop;
    m.deinit = aencoder_deinit;
    return &m;
}

媒体传输模块(ffmpge-server): server\ga\module\server-ffmpeg\server-ffmpeg.cpp

此模块不再应用。

ga_module_t *
module_load() {
    static ga_module_t m;
    //
    bzero(&m, sizeof(m));
    m.type = GA_MODULE_TYPE_SERVER;
    m.name = strdup("ffmpeg-rtsp-server");
    m.init = ff_server_init;
    m.start = ff_server_start;
    m.stop = ff_server_stop;
    m.deinit = ff_server_deinit;
    m.send_packet = ff_server_send_packet;
    //
    encoder_register_sinkserver(&m);
    //
    return &m;
}

音视频传输模块(live555): server\ga\module\server-live555\server-live555.cpp

以后应用此模块。Live555 存在性能优化空间,网上材料较多,可参考。

ga_module_t *
module_load() {
    static ga_module_t m;
    //
    bzero(&m, sizeof(m));
    m.type = GA_MODULE_TYPE_SERVER;
    m.name = strdup("live555-rtsp-server");
    m.init = live_server_init;
    m.start = live_server_start;
    m.stop = live_server_stop;
    m.deinit = live_server_deinit;
    m.send_packet = live_server_send_packet;
    //
    encoder_register_sinkserver(&m);
    //
    return &m;
}

控制指令输出模块

此模块反对键盘、鼠标事件输出,基于 SDL 实现。

ga_module_t *
module_load() {
    static ga_module_t m;
    bzero(&m, sizeof(m));
    m.type = GA_MODULE_TYPE_CONTROL;
    m.name = strdup("control-SDL");
    m.init = sdlmsg_replay_init;
    m.deinit = sdlmsg_replay_deinit;
    return &m;
}

Android 客户端

客户端由 C /C++ Native 库与 Java App 组成。

  • C/C++ Native: 提供触控指令传输、媒体流接管、解码、播放性能。
  • Java App: 提供触控指令采集、以及云游戏 App 性能。

C/C++ Native 库 API (主入口): server\ga\android\jni\src\libgaclient.h

Native 库 JNI 函数定义。

  1. 初始化
JNIEXPORT jboolean JNICALL Java_org_gaminganywhere_gaclient_GAClient_initGAClient(JNIEnv *env, jobject thisObj, jobject weak_this);
  1. 配置参数
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_resetConfig(JNIEnv *env, jobject thisObj);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_setProtocol(JNIEnv *env, jobject thisObj, jstring proto);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_setHost(JNIEnv *env, jobject thisObj, jstring host);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_setPort(JNIEnv *env, jobject thisObj, jint port);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_setObjectPath(JNIEnv *env, jobject thisObj, jstring objpath);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_setRTPOverTCP(JNIEnv *env, jobject thisObj, jboolean enabled);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_setCtrlEnable(JNIEnv *env, jobject thisObj, jboolean enabled);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_setCtrlProtocol(JNIEnv *env, jobject thisObj, jboolean tcp);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_setCtrlPort(JNIEnv *env, jobject thisObj, jint port);
JNIEXPORT void JNICALL
Java_org_gaminganywhere_gaclient_GAClient_setBuiltinAudioInternal(JNIEnv *env, jobject thisObj, jboolean enable);
JNIEXPORT void JNICALL
Java_org_gaminganywhere_gaclient_GAClient_setBuiltinVideoInternal(JNIEnv *env, jobject thisObj, jboolean enable);
JNIEXPORT void JNICALL
Java_org_gaminganywhere_gaclient_GAClient_setAudioCodec(
        JNIEnv *env, jobject thisObj,
        /*jstring codecname,*/ jint samplerate, jint channels);
JNIEXPORT void JNICALL
Java_org_gaminganywhere_gaclient_GAClient_setDropLateVideoFrame(JNIEnv *env, jobject thisObj, jint ms);
  1. 发送键盘鼠标事件
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_sendKeyEvent(JNIEnv *env, jobject thisObj, jboolean pressed, jint scancode, jint sym, jint mod, jint unicode);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_sendMouseKey(JNIEnv *env, jobject thisObj, jboolean pressed, jint button, jint x, jint y);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_sendMouseMotion(JNIEnv *env, jobject thisObj, jint x, jint y, jint xrel, jint yrel, jint state, jboolean relative);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_sendMouseWheel(JNIEnv *env, jobject thisObj, jint dx, jint dy);
  1. 媒体解码与播放
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_sendKeyEvent(JNIEnv *env, jobject thisObj, jboolean pressed, jint scancode, jint sym, jint mod, jint unicode);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_sendMouseKey(JNIEnv *env, jobject thisObj, jboolean pressed, jint button, jint x, jint y);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_sendMouseMotion(JNIEnv *env, jobject thisObj, jint x, jint y, jint xrel, jint yrel, jint state, jboolean relative);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_sendMouseWheel(JNIEnv *env, jobject thisObj, jint dx, jint dy);

GameAnywhere 对视频应用软解码(猜想出于兼容性思考),相比拟硬解码性能较差,可替换为硬解码。

  1. 媒体流连贯申请
JNIEXPORT jboolean JNICALL Java_org_gaminganywhere_gaclient_GAClient_rtspConnect(JNIEnv *env, jobject thisObj);
JNIEXPORT void JNICALL Java_org_gaminganywhere_gaclient_GAClient_rtspDisconnect(JNIEnv *env, jobject thisObj);

媒体流传输客户端(Live555 客户端): server\ga\android\jni\src\rtspclient.cpp

媒体流传输客户端是一个 RTSP 客户端,依赖 Live555 的库 (BasicUsageEnvironment, UsageEnvironment, groupsock, liveMedia)。

RTSP 客户端启动一个线程,通过 BasicTaskScheduler.SingleStep() 办法一直从服务端读取音视频数据,而后解码、播放。

读数据:

void *
rtsp_thread(void *param) {
    RTSPClient *client = NULL;
    BasicTaskScheduler0 *bs = BasicTaskScheduler::createNew();
    ...
    while(rtspParam->quitLive555 == 0) {bs->SingleStep(1000000);
    }
    ...
}

如果是视频数据,回调 play_video 函数:

static void
play_video(int channel, unsigned char *buffer, int bufsize, struct timeval pts, bool marker) {struct decoder_buffer *pdb = &db[channel];
    int left;
    //
    if(bufsize <= 0 || buffer == NULL) {rtsperror("empty buffer?\n");
        return;
    }
#ifdef ANDROID
    if(rtspconf->builtin_video_decoder != 0) {
        //////// Work with built-in decoders
        if(video_codec_id == AV_CODEC_ID_H264) {if(android_decode_h264(rtspParam, buffer, bufsize, pts, marker) < 0)
                return;
        } else if(video_codec_id == AV_CODEC_ID_VP8) {if(android_decode_vp8(rtspParam, buffer, bufsize, pts, marker) < 0)
                return;
        }
        image_rendered = 1;
    } else {
    //////// Work with ffmpeg
#endif
  ...
}

如果是音频数据,回调 play_audio 函数:

static void
play_audio(unsigned char *buffer, int bufsize, struct timeval pts) {
#ifdef ANDROID
    if(rtspconf->builtin_audio_decoder != 0) {android_decode_audio(rtspParam, buffer, bufsize, pts);
    } else {
    ////////////////////////////////////////
#endif
...
}

媒体流解码与播放: server\ga\android\jni\src\android-decoders.cpp

具体的媒体流解码逻辑在 android-decoders.cpp 中实现。包含配置解码器参数,启动解码器,解码一帧视频。

int android_prepare_audio(RTSPThreadParam *rtspParam, const char *mime, bool builtinDecoder);
int android_decode_audio(RTSPThreadParam *rtspParam, unsigned char *buffer, int bufsize, struct timeval pts);
int android_config_h264_sprop(RTSPThreadParam *rtspParam, const char *sprop);
int android_decode_h264(RTSPThreadParam *rtspParam, unsigned char *buffer, int bufsize, struct timeval pts, bool marker);
int android_decode_vp8(RTSPThreadParam *rtspParam, unsigned char *buffer, int bufsize, struct timeval pts, bool marker);

指令传输: server\ga\android\jni\src\controller.cpp

Java App 层发送的指令,被保留到队列中,由 controller.cpp 实现的线程将队列中指令发送给服务端。指令通道是一条独立通道。

Java App

Java App 绝对简略,提供界面与采集用户输出。

Reference

GameAnywhere On Github

GameAnyhwere Offical Site

更多云最佳实际 https://best.practices.cloud

退出移动版