scrcpy 开发环境搭建与源码走读之一

此系列博文主要记录本人在scrcpy项目重构过程中记录文件,共大家参考和备忘。

环境说明

  • 1.操作系统 ubuntu20-64
  • 2.虚拟机 VMware 15
  • 3.源码 1.19 版本
    git clone https://github.com/Genymobile/scrcpy -b v1.19
    cd scrcpy

第一步 搭建 scrcpy 编译开发环境

1.1 安装 meson

sudo apt install python3-pip
pip3 install meson

1.2 安装 ninja

sudo apt install ninja-build

1.3 安装 java vm

sudo apt install openjdk-11-jdk
export PATH=“ J A V A H O M E / b i n : JAVA_HOME/bin: JAVAHOME/bin:PATH”

1.4 安装 android sdk

android sdk安装,本人是通过在ubuntu20中安装 AndroidStudio-2021 的IDE环境,
然后通过adk管理方式,下载sdk包,内容如下:

robot@ubuntu:~$ ls Android/Sdk/platforms/
android-22  android-24  android-26  android-28  android-30  android-31
robot@ubuntu:~$ ls Android/Sdk/build-tools/
29.0.2  30.0.2  31.0.0

1.5 安装 依赖库

sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
                 libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev
sudo apt install ffmpeg libsdl2-2.0-0 adb
sudo snap install gradle

1.6 编译过程参考第四部分

参考说明问题,详细见:scrcpy源码主目录下BUILD.md 文件说明。

第二步 scrcpy 客户端程序走读

bool scrcpy(const struct scrcpy_options *options) {
    
    
	static struct scrcpy scrcpy;
	struct scrcpy *s = &scrcpy;
	server_init(&s->server);										///> 1. server_init()


	struct server_params params = {
    
    
		.serial = options->serial,
		.port_range = options->port_range,
		.bit_rate = options->bit_rate,
		.max_fps = options->max_fps,
		.display_id = options->display_id,
		.codec_options = options->codec_options,
		.encoder_name = options->encoder_name,
		.force_adb_forward = options->force_adb_forward,
	};
	server_start(&s->server, &params);								///> 2. server_start();

	server_started = true;
	sdl_init_and_configure(options->display, options->render_driver,
                                options->disable_screensaver);

	server_connect_to(&s->server, device_name, &frame_size);		///> 3. server_connect_to();


	file_handler_init(&s->file_handler, s->server.serial,
                               options->push_target);				///> 4. file_handler_init();  socket init & 服务端代码adb push

	decoder_init(&s->decoder);										///> 5. decoder_init();

	av_log_set_callback(av_log_callback);							///> 6. av_log_set_callback();

	static const struct stream_callbacks stream_cbs = {
    
    				///> 7. stream_init();
        .on_eos = stream_on_eos,
    };
    stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL);


    stream_add_sink(&s->stream, &dec->packet_sink);					///> 8. stream_add_sink(); dec

    stream_add_sink(&s->stream, &rec->packet_sink);					///> 9. stream_add_sink(); rec


    controller_init(&s->controller, s->server.control_socket);		///> 10. controller_init(); control_socket

    controller_start(&s->controller);								///> 11. controller_start();



    struct screen_params screen_params = {
    
    
            .window_title = window_title,
            .frame_size = frame_size,
            .always_on_top = options->always_on_top,
            .window_x = options->window_x,
            .window_y = options->window_y,
            .window_width = options->window_width,
            .window_height = options->window_height,
            .window_borderless = options->window_borderless,
            .rotation = options->rotation,
            .mipmaps = options->mipmaps,
            .fullscreen = options->fullscreen,
            .buffering_time = options->display_buffer,
    };
    screen_init(&s->screen, &screen_params);						///> 12. screen_init();

    decoder_add_sink(&s->decoder, &s->screen.frame_sink);			///> 13. decoder_add_sink();

#ifdef HAVE_V4L2
    sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size,
                               options->v4l2_buffer);				///> 14. sc_v4l2_sink_init();
    decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink);
#endif

    stream_start(&s->stream);         ///> 14+.流启动配置,第一次发布时遗漏咯,很抱歉.补充    
    input_manager_init(&s->input_manager, &s->controller, &s->screen, options);		///> 15. input_manager_init();

    ret = event_loop(s, options);													///> 16. event_loop();


    ///> 程序推出释放资源相关内容
    screen_hide_window(&s->screen);
    controller_stop(&s->controller);
    file_handler_stop(&s->file_handler);
    screen_interrupt(&s->screen);
    server_stop(&s->server);
    stream_join(&s->stream);
    sc_v4l2_sink_destroy(&s->v4l2_sink);
    screen_join(&s->screen);
    screen_destroy(&s->screen);
    controller_join(&s->controller);
    controller_destroy(&s->controller);
    recorder_destroy(&s->recorder);
    file_handler_join(&s->file_handler);
    file_handler_destroy(&s->file_handler);

    server_destroy(&s->server);       										///> 销毁 server
    return ret;
}    
  1. server_init()

struct scrcpy {
    
    						///> 封装 scrcpy 对象内容 
    struct server server;									//> 1. 通信的server对象
    struct screen screen;									//> 2. 投屏 screen 对象
    struct stream stream;									//> 3. 视频流 stream 对象
    struct decoder decoder;
    struct recorder recorder;
#ifdef HAVE_V4L2
    struct sc_v4l2_sink v4l2_sink;
#endif
    struct controller controller;
    struct file_handler file_handler;
    struct input_manager input_manager;
};

  1. server_start();
bool
server_start(struct server *server, const struct server_params *params) {
    
    

	push_server(params->serial);											///> 2.1 from client adb push to server 

	enable_tunnel_any_port(server, params->port_range,
                                params->force_adb_forward);					///> 2.2 开启ADB端口转发,走 WIFI 模式,否则认为是USB ADB 模式.

	server->process = execute_server(server, params);						///> 2.3 建立本地的 ADB client 连接本地 ADB SERVER 。

	bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
                               "wait-server", server);						///> 2.4 本地 ADB SERVER 建立与 ANDROID ADB DAEMAIN 守护线程通信。


}

获取本地环境变量中,是否有 “SCRCPY_SERVER_PATH” 内容,如果没有设置环境变量、则使用缺省的 server-path 内容(scrcpy软件开机执行内容);

  • push_server(params->serial); 函数源码走读
///> 2.1.  params->serial 是ADB PUSH中是用的 -s 参数.
static bool
push_server(const char *serial) {
    
    
	char *server_path = get_server_path();    /* 软件没有安装,使用当前路径下的 scrcpy-server 作为执行路径 */
	if (!server_path) {
    
    
        return false;
    }
    /* 
    *
    * adb push 
    * local_path = /build/server/scrcpy-server           编译输出的服务端程序。
    * remote_path="/data/local/tmp/scrcpy-server.jar"    ANDROID 设备路径和文件名称.
    *
    */

    process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);   

}

///> 2.2 adb_push 函数
process_t adb_push(const char *serial, const char *local, const char *remote) {
    
    

	const char *const adb_cmd[] = {
    
    "push", local, remote};
    process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));

}

///> 2.3. adb_execute 函数
process_t adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
    
    
	argv[0] = get_adb_command();
	argv[1] = "-s";
    argv[2] = serial;     ///> 
    memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
    enum process_result r = process_execute(argv, &process);
    return process;
}


///> 2.4. 建立管道、执行系统调用功能: /usr/share/adb push -s serial local_path remote_path 
enum process_result process_execute(const char *const argv[], pid_t *pid) {
    
    
	int fd[2];
	pipe(fd);
	pid = fork();

	fcntl(fd[1], F_SETFD, FD_CLOEXEC);
	execvp(argv[0], (char *const *)argv);

	write(fd[1], &ret, sizeof(ret));
	close(fd[1]);
	return ret;
}

  • enable_tunnel_any_port()
    软件运行检测是否开启ADB FORWORD 功能,如果开启则执行 enable_tunnel_any_port() 函数。

enable_tunnel_any_port(server, params->port_range,params->force_adb_forward)
{
    
    
	for (;;) {
    
    
		///> 遍历 adb port 列表,找出可以使用的端口。
		if (enable_tunnel_forward(server->serial, port)) {
    
    
            // success
            server->local_port = port;
            return true;
        }
	}

}

  • server->process = execute_server(server, params)
    通过ADB SHELL 运行 scrcpy-server 程序方法。
server->process = execute_server(server, params)
{
    
    
	 const char *const cmd[] = {
    
    
        "shell",
        "CLASSPATH=" DEVICE_SERVER_PATH, 								//> = "/data/local/tmp/scrcpy-server.jar",
        "app_process",
#ifdef SERVER_DEBUGGER
# define SERVER_DEBUGGER_PORT "5005"
# ifdef SERVER_DEBUGGER_METHOD_NEW
        /* Android 9 and above */
        "-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,"
        "server=y,address="
# else
        /* Android 8 and below */
        "-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
# endif
            SERVER_DEBUGGER_PORT,
#endif
        "/", // unused
        "com.genymobile.scrcpy.Server",
        SCRCPY_VERSION,
        log_level_to_server_string(params->log_level),
        max_size_string,
        bit_rate_string,
        max_fps_string,
        lock_video_orientation_string,
        server->tunnel_forward ? "true" : "false",
        params->crop ? params->crop : "-",
        "true", // always send frame meta (packet boundaries + timestamp)
        params->control ? "true" : "false",
        display_id_string,
        params->show_touches ? "true" : "false",
        params->stay_awake ? "true" : "false",
        params->codec_options ? params->codec_options : "-",
        params->encoder_name ? params->encoder_name : "-",
        params->power_off_on_close ? "true" : "false",
    };

    return adb_execute(server->serial, cmd, ARRAY_LEN(cmd));
}

///> adb_execute() 是ADB 接口的API函数
adb_execute(server->serial, cmd, ARRAY_LEN(cmd))
{
    
    

}

第三步 修改 scrcpy 客户端 adb.c 中代码,观察ADB执行过程内容

在 adb.c 文件的135行下,增加如下代码


memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
    argv[len + i] = NULL;
///> 增加代码如下:
    memset(buf,0,sizeof(buf));
    for(i=0;i <= (int)len; i++){
    
    
        memset(cmd,0,sizeof(cmd));
        sprintf(cmd,"%s ",argv[i]);
        length = strlen(cmd);
        memcpy(&buf[base],cmd,length);
        base += length;
    }
    printf("%s, ADB-CMD: %s \n",__FILE__,buf);
///> 增加代码结束

    enum process_result r = process_execute(argv, &process);
    if (r != PROCESS_SUCCESS) {
    
    
        show_adb_err_msg(r, argv);
        process = PROCESS_NONE;
    }

第四步 meson 编译源码与验证ANDROID 服务端程序启动逻辑

robot@ubuntu:~/scrcpy/scrcpy$ export ANDROID_SDK_ROOT=~/Android/Sdk
### (1).  meson 编译源码
robot@ubuntu:~/scrcpy/scrcpy$ meson build --buildtype release --strip -Db_lto=true
Directory already configured.

Just run your build command (e.g. ninja) and Meson will regenerate as necessary.
If ninja fails, run "ninja reconfigure" or "meson --reconfigure"
to force Meson to regenerate.

If build failures persist, run "meson setup --wipe" to rebuild from scratch
using the same options as passed when configuring the build.
To change option values, run "meson configure" instead.

### (2). ninja 链接编译结果
robot@ubuntu:~/scrcpy/scrcpy$ ninja -C build
ninja: Entering directory `build'
[0/3] Generating scrcpy-server with a custom command

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.3/userguide/command_line_interface.html#sec:command_line_warnings

BUILD SUCCESSFUL in 4s
24 actionable tasks: 1 executed, 23 up-to-date
[1/3] Compiling C object app/scrcpy.p/src_adb.c.o
../app/src/adb.c: In function 'adb_execute':
../app/src/adb.c:138:15: warning: comparison of integer expressions of different signedness: 'int' and 'size_t' {
    
    aka 'long unsigned int'} [-Wsign-compare]
  138 |     for(i=0;i <= len; i++){
    
    
      |               ^~
[2/3] Linking target app/scrcpy


### (3). 执行编译结果
robot@ubuntu:~/scrcpy/scrcpy$ ./run build

INFO: scrcpy 1.19 <https://github.com/Genymobile/scrcpy>
../app/src/adb.c, ADB-CMD:adb push build/server/scrcpy-server /data/local/tmp/scrcpy-server.jar     // 第一步 ADB PUSH 服务端程序到android手机的路径和文件名称.
build/server/scrcpy-server: 1 file pushed. 2.7 MB/s (37330 bytes in 0.013s)
../app/src/adb.c, ADB-CMD:adb reverse localabstract:scrcpy tcp:27183  

../app/src/adb.c, ADB-CMD:adb shell CLASSPATH=/data/local/tmp/scrcpy-server.jar\                    // 第二步 ADB SHELL CLASSPATH 方式启动 android 手机端程序.
                                              app_process / com.genymobile.scrcpy.Server 1.19 info 0 8000000 0 -1 false - true true 0 false false - - false  

[server] INFO: Device: HUAWEI PRA-AL00X (Android 8.0.0)
../app/src/adb.c, ADB-CMD:adb reverse --remove localabstract:scrcpy  
INFO: Renderer: opengl
INFO: OpenGL version: 3.3 (Compatibility Profile) Mesa 21.0.3
INFO: Trilinear filtering enabled
INFO: Initial texture: 1080x1920                                                                    // 第三步 通过 USB 方式运行 scrcpy 程序成功。

第五部 WIFI方式运行 scrcpy结果日志

### 打开 adb tcpip 5555 端口
robot@ubuntu:~/scrcpy/scrcpy$ adb tcpip 5555

### 链接手机的wifi网络
robot@ubuntu:~/scrcpy/scrcpy$ adb connect 192.168.5.107:5555
connected to 192.168.5.107:5555

### 通过 WIFI 链接手机上的 scrcpy 程序运行
robot@ubuntu:~/scrcpy/scrcpy$ ./build/app/scrcpy -s 192.168.5.107:5555 -b 5000000
INFO: scrcpy 1.19 <https://github.com/Genymobile/scrcpy>
../app/src/adb.c, ADB-CMD: adb -s 192.168.5.107:5555 push  
/usr/local/share/scrcpy/scrcpy-server:...shed. 1.3 MB/s (37330 bytes in 0.028s)
../app/src/adb.c, ADB-CMD: adb -s 192.168.5.107:5555 reverse  
error: more than one device/emulator
ERROR: "adb reverse" returned with value 1
WARN: 'adb reverse' failed, fallback to 'adb forward'
../app/src/adb.c, ADB-CMD: adb -s 192.168.5.107:5555 forward  
../app/src/adb.c, ADB-CMD: adb -s 192.168.5.107:5555 shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 1.19 info 0 5000000 0 -1 true - true true 0 false false -  
[server] INFO: Device: HUAWEI PRA-AL00X (Android 8.0.0)
../app/src/adb.c, ADB-CMD: adb -s 192.168.5.107:5555 forward  
INFO: Renderer: opengl
INFO: OpenGL version: 3.3 (Compatibility Profile) Mesa 21.0.3
INFO: Trilinear filtering enabled
INFO: Initial texture: 1080x1920

编者注: 就不贴图,第四部分程序运行日志已经非常清晰。
scrcpy的程序启动逻辑是源码执行结果,没有任何意义,部分读者可能会在网上看到相关分析,
请以此源码实际输出为准。
至于adb shell CLASSPATH 运行android 程序说明,请自行搜索知乎上的相关说明。

猜你喜欢

转载自blog.csdn.net/weixin_38387929/article/details/120956597