GameAnywhere is an open source cloud game platform, developed by Chun-Ying Huang in 2013, the first is used for graduation thesis research, recently with the cloud game outlet is in full bloom, this project attention and improvement, this paper introduces GameAnywhere code composition. The License of GameAnywhere is BSD3. You can modify the code and close the source. However, the project relies on many open source components, so you need to pay attention to the License.

The code structure

The directory structure

├─bin │ ├─config │ ├─ ├─ common │ ├─data │ ├─log │ ├─ ├─ ├─ bass │ ├─bin │ ├─NvCodec │ ├─ ├─ NVENC ├ ─ deps. # rely on posix component of Unix package │ └ ─ lib ├ ─ deps. SRC # based on component source │ └ ─ patches ├ ─ deps. Win32 # dependent component of Windows header files and libraries │ ├ ─ bin │ ├ ─ the include │ │ ├ ─ the include │ │ ├ ─ lib │ │ ├ ─ libavcodec │ │ ├ ─ libavdevice │ │ ├ ─ libavfilter │ │ ├ ─ libavformat │ │ ├ ─ libavutil │ │ ├ ─ libpostproc │ │ ├ ─ libswresample │ │ ├ ─ libswscale │ │ ├ ─ live555 │ │ ├ ─ NVENC │ │ └ ─ SDL2 │ └ ─ lib ├ ─ docs └ ─ ga ├ ─ android # │ ├─ Asource system │ ├─ Ctrl-sDL # │ ├─ Encoder - Audio # ├─ Encoder - MFX │ ├─ Encoder - Nvenc # NVIDIA Hard Coding │ ├─ Encoder - Video # FFmpeg soft coding │ ├─ Encoder - Video # FFmpeg soft coding │ ├─ Encoder - Video # │ ├─ Heavy Metal Flag School │ ├─ Heavy Metal Flag School │ ├─ Heavy Metal Flag School │ ├─ Heavy metal Flag School │ ├─ ├─ Server-Live555 # ├─ Server-Live555 # │ ├─ vsource-Desktop # ├─ Server │ ├─ Event-Posix # │ ├ ─periodic # ├ ─periodic # ├─ ├─ Encoder ├─ Nvenc ├─ Ga/Core ├─ Microsoft ├─ Microsoft ├─ Microsoft ├─ Microsoft ├─ Microsoft ├─ Bass Exercises ├ ─ ga - hook ├ ─ ga - server - the event - driven ├ ─ ga - server - periodic ├ ─ groupsock ├ ─ ipch ├ ─ libga ├ ─ liveMedia ├ ─ the module - asource - system ├─ Module-Ctrl-SDL ├─ Module-Encoder - Audio ├─ Module-Encoder - Video ├─ Module-Encoder - X264 ├─ Module-Filter - RGB2YUV ├ ─ the module - server - ffmpeg ├ ─ the module - server - live555 ├ ─ the module - vsource - desktop ├ ─ the module - vsource - desktop - d ├ ─ the module - vsource - desktop - DFM └ ─ UsageEnvironmentCopy the code

The service side

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

Functions of the Server side: image collection, image preprocessing, sound collection, keyboard input, audio and video coding, packet, transmission function, these functions are respectively implemented by the corresponding GameAnywhere defined module.

//
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;
}

Copy the code

GameAnywhere module type definition: 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 */
};
Copy the code

GameAnywhere module definition: 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;
Copy the code

Module lifecycle action: 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);
Copy the code

Image acquisition module: Server \ GA \ Module \vsource-desktop

This module provides desktop level image collection, but does not provide process level image collection. DirectX and GDI two image acquisition implementation.

ga_module_t *
module_load(a) {
	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;
}
Copy the code

Image preprocessing module: Server \ GA \ Module \ filter-rGB2yuv \ filter-rGB2yuv.cpp

This module provides the conversion of captured RGB images to YUV format.

ga_module_t *
module_load(a) {
	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;
}
Copy the code

NVIDIA hard coding module: Server \ga\module\encoder-nvenc\encoder-nvenc.cpp

ga_module_t *
module_load(a) {
	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;
}
Copy the code

Soft coding module (FFMPGE implementation): server\ga\module\encoder-video\encoder-video.cpp

ga_module_t *
module_load(a) {
	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;
}
Copy the code

Soft coding module (X264 implementation): Server \ga\module\encoder-x264\encoder-x264. CPP

ga_module_t *
module_load(a) {
	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;
}
Copy the code

Audio acquisition module: Server \ga\module\asource-system\asource-system.cpp

On Windows, the Microsoft IAudioClient API is used to collect sound.

ga_module_t *
module_load(a) {
	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;
}
Copy the code

Audio encoding module: Server \ga\module\encoder-audio\encoder-audio. CPP

Encodes audio using FFMPEG encapsulated APIS in LAME/OPUS format.

ga_module_t *
module_load(a) {
	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;
}
Copy the code

Media Transfer module (FFMPGE-server): server\ga\ Module \server- FFmpeg \server-ffmpeg.cpp

This module is no longer used.

ga_module_t *
module_load(a) {
	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;
}
Copy the code

Audio and Video Transmission module (LIVE555): server\ga\ Module \server-live555\server-live555.cpp

This module is currently used. Live555 has performance optimization space, more online information, you can refer to.

ga_module_t *
module_load(a) {
	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;
}
Copy the code

Control instruction input module

This module supports keyboard, mouse event input, based on SDL implementation.

ga_module_t *
module_load(a) {
	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;
}
Copy the code

The Android client

The client consists of C/C++ Native library and Java App.

  • C/C++ Native: provides touch command transmission, media stream reception, decoding, and playback functions.
  • Java App: provides touch command collection and cloud game App functions.

C/C++ Native API (main entry): Server \ga\ Android \jni\ SRC \libgaclient.h

Native library JNI function definition.

  1. Initialize the
JNIEXPORT jboolean JNICALL Java_org_gaminganywhere_gaclient_GAClient_initGAClient( JNIEnv *env, jobject thisObj, jobject weak_this);
Copy the code
  1. Configuration parameters
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);
Copy the code
  1. Sends keyboard and mouse events
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);
Copy the code
  1. Media decoding and playback
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);
Copy the code

GameAnywhere uses soft decoding for video (presumably for compatibility reasons), which is less performance than hard decoding and can be replaced with hard decoding.

  1. Media stream connection request
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);
Copy the code

Media streaming client (Live555 client): Server \ga\ Android \jni\ SRC \ rtspClient.cpp

Media streaming client is an RTSP client that relies on Live555 library (BasicUsageEnvironment, UsageEnvironment, GroupSock, liveMedia).

RTSP client to start a thread, through BasicTaskScheduler. SingleStep () method from the service side read audio and video data, then the decoding and playing.

Read data:

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

If it is video data, call 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. }Copy the code

If it is audio data, call 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. }Copy the code

Media stream decoding and playback: Server \ga\ Android \jni\ SRC \ Android-decoders.cpp

The specific media stream decoding logic is implemented in Android -decoders. CPP. This includes configuring the decoder parameters, starting the decoder, and decoding a frame of video.

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);
Copy the code

Command transmission: Server \ga\ Android \jni\ SRC \controller.cpp

The instructions sent by the Java App layer are stored in a queue, and the thread implemented by Controller.cpp sends the queue instructions to the server. An instruction channel is a separate channel.

Java App

Java apps are relatively simple, providing an interface and capturing user input.

Reference

GameAnywhere On Github

GameAnyhwere Offical Site

More cloud best practices best practices