此系列博文主要记录本人在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, ¶ms); ///> 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;
}
- 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;
};
- 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 程序说明,请自行搜索知乎上的相关说明。