STF 基于 Scrcpy 的远程调试方案

wenxiaomao · 2019年12月13日 · 最后由 hbwang19 回复于 2023年06月15日 · 27123 次阅读
本帖已被设为精华帖!

基于 Scrcpy 的远程调试方案

前言

感谢 STF 的开源,让 Android 设备远程控制变得简单,STF 通过 minicap 和 minitouch 实现设备的显示和控制。
STF 在实际使用中会发现一些棘手的问题
1.电视不支持 minitouch
2.新手机比如 mi8 mi9 不支持 minicap
3.Android 发布新版本需要适配 minicap

分享一个新的方法,来弥补这些不足
演示效果如下,由于图片较大(14MB),手机党请谨慎点击如下链接,建议完全加载完在播放,否则会卡,电脑的录屏软件很不给力,渣渣画质。。。
https://github.com/wenxiaomao1023/scrcpy/blob/master/assets/out.gif

Scrcpy

app 目录 运行在 PC 端,对于 web 远程控制,这部分是不需要的
server 目录 运行在手机端,提供屏幕数据,接收并响应控制事件

Scrcpy 对比 minicap

1.获取 frame 数据方式是一致的(sdk19 以上)
2.Scrcpy 将 frame 编码 h264
3.minicap 将 frame 编码 jpeg

Scrcpy 处理方式看起来会更好,但是有一个问题,他的设计是将屏幕数据直接发给 PC,然后在 PC 上解码显示,这种方式在网页上却很不好展示。

调研与尝试

1.Broadway 在前端解码 h264 并显示
2.wfs.js 在前端将 h264 转成 mp4 送给 h5 MSE 实现播放,这种类似直播,B 站 flv.js 那种
以上两种尝试都获得了图像,但个人感觉,以上两个方案感觉都有坑,还需要大量优化才能脱坑

解决方法

当前摸索出的解决方法,Scrcpy 将 frame 编码 jpeg 发给前端然后通过画布展示,浏览器兼容好,可行性高,minicap 也是这么做的,修改方式见如下
https://github.com/wenxiaomao1023/scrcpy/commit/46d1c009d8ce559dc1ac1cdfceb234f1c7728498

当前已实现的功能

1.使用 ImageReader 获取 frame 数据,通过 libjpeg-turbo 编码 jpeg
2.控制帧率,压缩率,缩放比例,可以减少带宽占用,提高流畅性
3.考虑到当前大多是 minicap 的方案,所以 scrcpy 返回的屏幕数据格式兼容了 minicap 的数据格式(banner+jpegsize+jpegdata),移植改动会很小
4.最低支持到 Android4.4(adb forward 命令有变化,见下文,默认端口 6612)
5.返回旋转状态(为了替换 STFService.apk)
6.添加获取 DumpHierarchy 信息(启动命令加-D 参数),获取界面布局信息为录制 Case 功能准备

优点

1.德芙般丝滑,手机播放视频一点不卡,web 端展示也很流畅(30 - 50 FPS)
2.支持电视 touch
3.支持 mi8,mi9 等图像展示,不必在适配 minicap.so 啦,耶!

缺点

1.最低支持 android5.0,由于还依赖 android.system.Os,若想兼容低版本设备需要配合 minicap 使用,当前最低可支持到 Android4.4
2.流量问题,如果你的网站部署在内网,建议参考此方案,如果部署在公网,建议采用原生的 Scrcpy 方式

编译 libjpeg-turbo

我已经编好了 ARMv7 (32-bit) 和 ARMv8 (64-bit)
https://github.com/wenxiaomao1023/scrcpy/tree/master/server/libs/libturbojpeg/prebuilt
如果你需要其他平台,可参考此文档 Building libjpeg-turbo for Android 部分
https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/BUILDING.md
如果不需要,可跳过此步骤

编译 Scrcpy 代码

ninja 编译方式

android sdk 里有 ninja,如 Android/Sdk/cmake/3.6.4111459/bin/ninja,加到环境变量里即可,meson 需要安装
如果不想安装这些,可以往下看,用 gradle 编译

git clone https://github.com/wenxiaomao1023/scrcpy.git
cd scrcpy
meson x --buildtype release --strip -Db_lto=true
ninja -Cx

编译后会在 scrcpy 目录下生成
x/server/scrcpy-server.jar
server/jniLibs/armeabi-v7a/libcompress.so
server/jniLibs/arm64-v8a/libcompress.so

gradle 编译方式

git clone https://github.com/wenxiaomao1023/scrcpy.git
cd scrcpy/server
../gradlew assembleDebug

编译后会在 scrcpy 目录下生成
server/build/outputs/apk/debug/server-debug.apk
server/jniLibs/armeabi-v7a/libcompress.so
server/jniLibs/arm64-v8a/libcompress.so

server/build/outputs/apk/debug/server-debug.apkx/server/scrcpy-server.jar 是一样的,下文中都按 scrcpy-server.jar 命名方式进行说明

启动 scrcpy-server.jar

# 先看下设备的abi,
adb shell getprop ro.product.cpu.abi

新版本添加-L 参数,LD_LIBRARY_PATH 的值等于-L 参数的返回值,并追加:/data/local/tmp

# armeabi-v7a
adb push scrcpy/server/jniLibs/armeabi-v7a/libcompress.so /data/local/tmp/
adb push scrcpy/server/libs/libturbojpeg/prebuilt/armeabi-v7a/libturbojpeg.so /data/local/tmp/
adb push scrcpy/x/server/scrcpy-server.jar /data/local/tmp/
adb shell chmod 777 /data/local/tmp/scrcpy-server.jar
adb shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server -L
# LD_LIBRARY_PATH的值为上步-L的返回值加:/data/local/tmp(注意有个英文冒号)
adb shell LD_LIBRARY_PATH=???:/data/local/tmp CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server
# arm64-v8a
adb push server/jniLibs/arm64-v8a/libcompress.so /data/local/tmp/
adb push server/libs/libturbojpeg/prebuilt/arm64-v8a/libturbojpeg.so /data/local/tmp/
adb push scrcpy/x/server/scrcpy-server.jar /data/local/tmp/
adb shell chmod 777 /data/local/tmp/scrcpy-server.jar
adb shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server -L
# LD_LIBRARY_PATH的值为上步-L的返回值加:/data/local/tmp(注意有个英文冒号)
adb shell LD_LIBRARY_PATH=???:/data/local/tmp CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server
# Android4.4设备
# 前几步同上(查看abi push libcompress.so libturbojpeg.so,push scrcpy-server.jar,chmod scrcpy-server.jar)
# 不同的地方在于需要指定ANDROID_DATA参数,否则启动会报错Dex cache directory isn't writable: /data/dalvik-cache (Permission denied) uid=2000 gid=2000
# 见解决方法https://stackoverflow.com/questions/21757935/running-android-java-based-command-line-utility-from-adb-shell
adb shell mkdir -p /data/local/tmp/dalvik-cache
adb shell ANDROID_DATA=/data/local/tmp CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server -L
# LD_LIBRARY_PATH的值为上步-L的返回值加:/data/local/tmp(注意有个英文冒号)
adb shell ANDROID_DATA=/data/local/tmp LD_LIBRARY_PATH=???:/data/local/tmp CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server

app_process / com.genymobile.scrcpy.Server 这个命令可以设置如下参数,建议使用命令如下,压缩质量 60,最高 24 帧,480P
app_process / com.genymobile.scrcpy.Server -Q 60 -r 24 -P 480

Usage: [-h]

jpeg:
  -r <value>:    Frame rate (frames/sec).
  -P <value>:    Display projection (1080, 720, 480, 360...).
  -Q <value>:    JPEG quality (0-100).

  -c:            Control only.
  -L:            Library path.
  -D:            Dump window hierarchy.
  -h:            Show help.

启动 app.js

scrcpy-server.jar 兼容了 minicap 数据格式,可以直接用 minicap 的 demo app.js 看效果
https://github.com/openstf/minicap/tree/master/example
https://github.com/openstf/minicap/blob/master/example/app.js

需要把 app.js 改一下,多一个连接,修改如下

// 原始代码默认的图像socket
var stream = net.connect({
    port: 1717
})

// 修改1 加一个控制socket,在默认的net.connect后再net.connect一次(必须)
var controlStream = net.connect({
    port: 1717
})

// 修改2 新版本除了返回图像,还会返回旋转和层级(如果-D),需要容错处理,屏蔽掉exit(),并加else
if (frameBody[0] !== 0xFF || frameBody[1] !== 0xD8) {
    console.error('Frame body does not start with JPG header', frameBody)
    // process.exit(1) //屏蔽exit()
}
else { // 添加else
    ws.send(frameBody, {
        binary: true
    })
}
git clone https://github.com/openstf/minicap.git
cd minicap/example
npm install
# 注意这里要改为localabstract:scrcpy(旧版本)
# 注意这里要改为tcp:6612(新版本)
adb forward tcp:1717 tcp:6612
node app.js

访问 http://127.0.0.1:9002
如果有时刷新会没效果,只显示一个红框,先确认是否执行了 adb forward tcp:1717 tcp:6612,如果是概率性刷新不出图像这是正常的,客户端连接失败需要做重连操作,例子里并没有,所以简单解决办法是重启下 scrcpy-server.jar,然后刷新网页

Scrcpy touch

Scrcpy touch 的实现可以参考如下实现,当前实现常用的三种事件消息
// 键值 HOME,BACK,MENU 等
CONTROL_MSG_TYPE_INJECT_KEYCODE
// 点击和滑动
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT
// 鼠标滚轮滚动
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT

后端提供 json 格式接口

package main

import (
    "errors"
    "net"
    "github.com/qiniu/log"
    "bytes"
    "encoding/binary"
)

type MessageType int8
const (
    CONTROL_MSG_TYPE_INJECT_KEYCODE MessageType = iota
    CONTROL_MSG_TYPE_INJECT_TEXT                   
    CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT            
    CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT           
    CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON             
    CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL     
    CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL   
    CONTROL_MSG_TYPE_GET_CLIPBOARD                 
    CONTROL_MSG_TYPE_SET_CLIPBOARD                 
    CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE         
)

type PositionType struct {
    X        int32    `json:"x"`
    Y        int32    `json:"y"`
    Width    int16    `json:"width"`
    Height   int16    `json:"height"`
}

type Message struct {
    Msg_type                        MessageType     `json:"msg_type"`
// CONTROL_MSG_TYPE_INJECT_KEYCODE
    Msg_inject_keycode_action       int8            `json:"msg_inject_keycode_action"`
    Msg_inject_keycode_keycode      int32           `json:"msg_inject_keycode_keycode"`
    Msg_inject_keycode_metastate    int32           `json:"msg_inject_keycode_metastate"`
// CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT
    Msg_inject_touch_action         int8            `json:"msg_inject_touch_action"`
    Msg_inject_touch_pointerid      int64           `json:"msg_inject_touch_pointerid"`
    Msg_inject_touch_position       PositionType    `json:"msg_inject_touch_position"`
    Msg_inject_touch_pressure       uint16          `json:"msg_inject_touch_pressure"`
    Msg_inject_touch_buttons        int32           `json:"msg_inject_touch_buttons"`
// CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT
    Msg_inject_scroll_position      PositionType    `json:"msg_inject_scroll_position"`
    Msg_inject_scroll_horizontal    int32           `json:"msg_inject_scroll_horizontal"`
    Msg_inject_scroll_vertical      int32           `json:"msg_inject_scroll_vertical"`
}

type KeycodeMessage struct {
    Msg_type                        MessageType     `json:"msg_type"`
    Msg_inject_keycode_action       int8            `json:"msg_inject_keycode_action"`
    Msg_inject_keycode_keycode      int32           `json:"msg_inject_keycode_keycode"`
    Msg_inject_keycode_metastate    int32           `json:"msg_inject_keycode_metastate"`
}

type TouchMessage struct {
    Msg_type                        MessageType     `json:"msg_type"`
    Msg_inject_touch_action         int8            `json:"msg_inject_touch_action"`
    Msg_inject_touch_pointerid      int64           `json:"msg_inject_touch_pointerid"`
    Msg_inject_touch_position       PositionType    `json:"msg_inject_touch_position"`
    Msg_inject_touch_pressure       uint16          `json:"msg_inject_touch_pressure"`
    Msg_inject_touch_buttons        int32           `json:"msg_inject_touch_buttons"`
}

type ScrollMessage struct {
    Msg_type                        MessageType     `json:"msg_type"`
    Msg_inject_scroll_position      PositionType    `json:"msg_inject_scroll_position"`
    Msg_inject_scroll_horizontal    int32           `json:"msg_inject_scroll_horizontal"`
    Msg_inject_scroll_vertical      int32           `json:"msg_inject_scroll_vertical"`
}

func drainScrcpyRequests(conn net.Conn, reqC chan Message) error {
    for req := range reqC {
        var err error
        switch req.Msg_type {
        case CONTROL_MSG_TYPE_INJECT_KEYCODE:
            t := KeycodeMessage{
                Msg_type: req.Msg_type, 
                Msg_inject_keycode_action: req.Msg_inject_keycode_action,
                Msg_inject_keycode_keycode: req.Msg_inject_keycode_keycode,
                Msg_inject_keycode_metastate: req.Msg_inject_keycode_metastate,
            }
            buf := &bytes.Buffer{}
            err := binary.Write(buf, binary.BigEndian, t)
            if err != nil {
                log.Debugf("CONTROL_MSG_TYPE_INJECT_KEYCODE error: %s", err)
                log.Debugf("%s",buf.Bytes())
                break
            }
            _, err = conn.Write(buf.Bytes())
        case CONTROL_MSG_TYPE_INJECT_TEXT:
        case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
            var pointerid int64 = -1
            var pressure uint16 = 65535
            var buttons int32 = 1
            req.Msg_inject_touch_pointerid = pointerid
            req.Msg_inject_touch_pressure = pressure
            req.Msg_inject_touch_buttons = buttons
            t := TouchMessage{
                Msg_type: req.Msg_type, 
                Msg_inject_touch_action: req.Msg_inject_touch_action, 
                Msg_inject_touch_pointerid: req.Msg_inject_touch_pointerid, 
                Msg_inject_touch_position: PositionType{
                    X: req.Msg_inject_touch_position.X, 
                    Y: req.Msg_inject_touch_position.Y, 
                    Width: req.Msg_inject_touch_position.Width,
                    Height: req.Msg_inject_touch_position.Height,
                }, 
                Msg_inject_touch_pressure: req.Msg_inject_touch_pressure, 
                Msg_inject_touch_buttons: req.Msg_inject_touch_buttons,
            }
            buf := &bytes.Buffer{}
            err := binary.Write(buf, binary.BigEndian, t)
            if err != nil {
                log.Debugf("CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT error: %s", err)
                log.Debugf("%s",buf.Bytes())
                break
            }
            _, err = conn.Write(buf.Bytes())
        case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
            t := ScrollMessage{
                Msg_type: req.Msg_type, 
                Msg_inject_scroll_position: PositionType{
                    X: req.Msg_inject_scroll_position.X, 
                    Y: req.Msg_inject_scroll_position.Y, 
                    Width: req.Msg_inject_scroll_position.Width,
                    Height: req.Msg_inject_scroll_position.Height,
                }, 
                Msg_inject_scroll_horizontal: req.Msg_inject_scroll_horizontal, 
                Msg_inject_scroll_vertical: req.Msg_inject_scroll_vertical, 
            }
            buf := &bytes.Buffer{}
            err := binary.Write(buf, binary.BigEndian, t)
            if err != nil {
                log.Debugf("CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT error: %s", err)
                log.Debugf("%s",buf.Bytes())
                break
            }
            _, err = conn.Write(buf.Bytes())
        case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
        case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
        case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
        case CONTROL_MSG_TYPE_GET_CLIPBOARD:
        case CONTROL_MSG_TYPE_SET_CLIPBOARD:
        case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
        default:
            err = errors.New("unsupported msg type")
        }
        if err != nil {
            return err
        }
    }
    return nil
}

前端调用

let scrcpyKey = (key) => {
    ws.send(JSON.stringify({
        "msg_type": 0,
        "msg_inject_keycode_action": 0,
        "msg_inject_keycode_keycode": key,
        "msg_inject_keycode_metastate": 0
    }))
    ws.send(JSON.stringify({
        "msg_type": 0,
        "msg_inject_keycode_action": 1,
        "msg_inject_keycode_keycode": key,
        "msg_inject_keycode_metastate": 0
    }))
}
let scrcpyTouchDown = (touch) => {
    ws.send(JSON.stringify({
        "msg_type": 2,
        "msg_inject_touch_action": 0,
        "msg_inject_touch_position": {
            "x": touch.x, "y": touch.y, "width": touch.w, "height": touch.h
    }}));
}
let scrcpyTouchMove = (touch) => {
    ws.send(JSON.stringify({
        "msg_type": 2,
        "msg_inject_touch_action": 2,
        "msg_inject_touch_position": {
            "x": touch.x, "y": touch.y, "width": touch.w, "height": touch.h
        }
    }));
}
let scrcpyTouchUp = (touch) => {
    ws.send(JSON.stringify({
        "msg_type": 2,
        "msg_inject_touch_action": 1,
        "msg_inject_touch_position": {
            "x": touch.x, "y": touch.y, "width": touch.w, "height": touch.h
        }
    }));
}
//向下滚动
let scrcpyScrollDown = (touch) => {
    ws.send(JSON.stringify({
        "msg_type": 3,
        "msg_inject_scroll_position": {
            "x": touch.x, "y": touch.y, "width": touch.w, "height": touch.h
        },
        "msg_inject_scroll_horizontal": 0,
        "msg_inject_scroll_vertical": -1,
    }));
}
//向上滚动
let scrcpyScrollUp = (touch) => {
    ws.send(JSON.stringify({
        "msg_type": 3,
        "msg_inject_scroll_position": {
            "x": touch.x, "y": touch.y, "width": touch.w, "height": touch.h
        },
        "msg_inject_scroll_horizontal": 0,
        "msg_inject_scroll_vertical": 1,
    }));
}

项目还在开发阶段,欢迎反馈问题 : )

收工~

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 107 条回复 时间 点赞
思寒_seveniruby 将本帖设为了精华贴 12月15日 20:02

很不错的样子

编译后的 jar 怎么集成至 stf 替换目前的 minicap?

可以找您定做个方案吗?18514000999 VX

@codeskyblue 这下可以接入 atxserver2 了👏 👏 👏

手机上的 cpu 资源占用怎么样

大浪 回复

没有用 STF,没法帮你了

codeskyblue 回复

CPU 占用还是比较高的,目前看界面效果还可以接受

wenxiaomao 回复

只要不比 minicap 高太多就行

一看名字就是猜到了,扫了下二维码还真是,你从乐视出来后也有几年几年没见了。

不错的方案,不过昨天 Win 下编译一堆问题,看来 Win 环境还是不适合。

试了一下,主要是启动 db shell LD_LIBRARY_PATH=/system/lib64:/vendor/lib64:/data/local/tmp CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 命令的时候会报这个问题

CANNOT LINK EXECUTABLE "/system/bin/dex2oat": "/system/lib64/libart-compiler.so" is 64-bit instead of 32-bit

会影响吗

仅楼主可见
simple [精彩盘点] TesterHome 社区 2019 年 度精华帖 中提及了此贴 12月24日 22:45
zerkzzzz 回复

请问下你用的什么设备,我只在小米设备上做了验证,没有其他品牌的
可以提供下 abi 信息吗 adb shell getprop | grep abi
或将此参数改为这样再试下呢 LD_LIBRARY_PATH=/system/lib:/vendor/lib:/data/local/tmp

zerkzzzz 回复

看下 logcat 是否有报错 adb shell logcat -v threadtime | grep scrcpy
感觉与之前启动报错有关,scrcpy-server 没有起来,scrcpy touch 可用吗?

wenxiaomao 回复

编译完成,启动 app_process 后正常情况下 logcat 的日志是
12-28 14:36:09.564 7554 7554 I scrcpy : Options frame rate: 24 (1 ~ 100)
12-28 14:36:09.565 7554 7554 I scrcpy : Options quality: 50 (1 ~ 100)
12-28 14:36:09.565 7554 7554 I scrcpy : Options scale: 2 (1,2,4...)

然后实际要打开页面去链接才会有 fps 变化是吗?
后面启动 app.js,感觉不是很清晰,还请能否出个 demo 呢?是否做了其他变更?
scrcpy 用 openstf minicap 的测试代码可以支持吗?

adb shell getprop | grep abi 的信息如下,用的是 coolpad 手机

node app.js 执行之后访问端口,控制台就出现这个问题,看起来是 stream 断开了 是否 forward 需要特殊设置?

12-28 14:42:08.296 7554 7554 D scrcpy : Screen streaming stopped
12-28 14:42:08.298 7554 7744 I scrcpy : =========================================>>>
12-28 14:42:08.298 7554 7744 I scrcpy : head: 0
12-28 14:42:08.298 7554 7744 I scrcpy : rawBuffer.length: 1024
12-28 14:42:08.299 7554 7744 I scrcpy : rawBuffer: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
12-28 14:42:08.299 7554 7744 I scrcpy : =========================================<<<
12-28 14:42:08.299 7554 7744 D scrcpy : Controller stopped
12-28 14:42:08.300 7554 7744 D scrcpy : E:Controller socket closed

仅楼主可见
21楼 已删除
zerkzzzz 回复

图片不清晰请尝试将启动命名修改为如下,主要是修改这 2 个参数-Q 和-P
app_process / com.genymobile.scrcpy.Server -Q 100 -r 60 -P 1

看日志 scrcpy-server 启动正常,后台报错应该和你刷新网页有关,要注意下 minicap 提供的 demo 只是一次请求有效,没有做界面刷新处理,所以如果你刷新了界面,需要将 scrcpy-server.jar 重新启动一次,forward 不用变,只需 forward 一次即可,希望可以帮到你

wenxiaomao 回复

可以加您微信吗 大神

可以问下,通过这种方式的实现,有办法在 scrcoy 运行过程中更改清晰度吗?

大神你好,想问下 controller 方面可以直接通过端口发送控制消息吗?就是利用 scrcpy 的 controller 线程进行 touch

大浪 回复

需要修改一下 stf adb.push 那一块的代码,和 stream.js 的代码,另外 scrcpy 的设备控制模块要去掉,不然画面传输也起不来。

qchuang 回复

你这边成功了么?写个文档学习下呀👍

大浪 回复

我这边现在是基于 scrcpy + inputmanager,但是 web 端解析播放.h264 这一块跟楼主一样遇到了很多坑,还是爬坑中,爬完坑可以写个文档大家一起交流一下。

wpx 回复

目前运行过程中是不改清晰度的,当屏幕旋转会按照启动时配置的参数值重新设置清晰度,可以在这里改清晰度

Zayoi 回复

可以,按照控制消息的格式,向 socket 发数据即可,按照 server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java next() 读取的格式写将数据发到 socket 就可以控制了

修改问题,添加更新,欢迎反馈 : )
启动命令有变化,adb forward 命令有变化,默认端口 6612,详见上文
4.最低支持到 Android4.4
5.返回旋转状态(为了替换 STFService.apk)
6.添加获取 DumpHierarchy 信息(启动命令加-D 参数),获取界面布局信息为录制 Case 功能准备

32楼 已删除
33楼 已删除
34楼 已删除

现在项目代码里的 ImageReader 的 Surface 如何获取屏幕内容,执行代码总是进入不了 ImageReader 的监听方法 onImageAvailable, 刚入门的安卓小白,请大神指点一二,非常感谢

37楼 已删除

LD_LIBRARY_PATH 中/system/lib 的作用是什么呢,我在使用时,会由于这个库导致一些手机上出现错误

wpx 回复

指定加载库的位置,新版本请使用-L 参数获取 LD_LIBRARY_PATH 的值,如果还会出现错误,请帮忙贴下错误日志和设备信息

请问 app.js 为啥要 // 修改 1 加一个控制 socket,在默认的 net.connect 后再 net.connect 一次(必须)

Jacc 回复

第一个 net.connect 是传图像和旋转状态等信息,第二个 net.connect 是传控制信息 (点击滑动等)

其实对于 minicap 或者 scrcpy 总有个想法,是否可以开发个 app 来控制转发原始数据流到指定域名接口,那任何设备就可以插拔式的做云真机

qchuang 回复

大胸弟,怎样了 成功了没

你好请问这个框架支持电视机安卓系统的自动化吗

为啥没人尝试直播 sdk 呢,手机端采用推流,客户端采用拉流,可以支持同一个设备同时被多方同时看 😬

请问下你做的这个是一套远程真机操作系统吗


这两部分代码在哪个工程或者文件里面添加呢

wenxiaomao 回复

现在 web 端可以展示出手机页面了,但是还不可以操作手机页面,这个应该怎么修改呢, 主要是为了解决手机没有触屏模块,远程 stf 无法操作手机的问题。

支持使用 SurfaceView 播放视频的播放器截图不?

请问目前的方案能够实现类似于 Minicap 截图的功能么?

51楼 已删除

writeRotation(fd)
新版屏幕方向这个如果不需要的话可以注释掉,这样就不需要修改 app.js

楼主你好,我记得在 18 年还是 19 年初,你发的是可以基于 web 远程调试的,现在已经不更了吗?

你好,大佬,我想问下,目前你这个项目 scrcpy 有持续跟官网保持同步更新吗?因为我现在也是用这个项目的,用 win 编译的。另外也想请教下,官网的项目如何进行修改进行 win 编译可用的。感谢~

scrcpy 是使用什么语言

学到了🚗

58楼 已删除
仅楼主可见

接口测试工具可以试用一下国产的接口测试和接口文档生产工具 apipost:https://www.apipost.cn/?dt=2020

yca 基于 Scrcpy 的云测平台改造方案 中提及了此贴 12月30日 17:51

ninja编译方式编译后没有出现下面的文件!!!!!!
x/server/scrcpy-server.jar
server/jniLibs/armeabi-v7a/libcompress.so
server/jniLibs/arm64-v8a/libcompress.so
楼主编译好的arm64-v8aarmeabi-v7a文件下也没有libcompress.so文件
请问大佬arm64-v8a文件下的libjpeg.so是不是libcompress.so文件,只是名字不同

多谢! 图片模块已经移植测试完成,明天开始移植 touch 模块

793136254 回复

你好可以加你的 QQ 了解下图片模块(我重新编译楼主的代码没有出现 libcompress.so 文件)qq:1137136417

请问下有没有尝试过使用 scrcpy 替换 atxserver 中的 minicap,暂时还不知道怎么弄,想请教下,请问有没有可以加你的微信或者 QQ 吗

郭军 回复

图片模块跟 minicap 连接基本上是一样的 ,只需要判断一下 frameBodyLength,如果不是图片的话解析成 orientation 就可以了。

Kyle 回复

楼主这个方案已经实现了,自己编译一下 so jar,修改一下连接方式就可以了

68楼 已删除
69楼 已删除
gkd 回复

已加你的 QQ,能直接请教下吗

gkd 回复

我编译成功但是连接失败报错,可以加下你微信请教下🎊

郭军 回复

socket 需要连接两次

Kyle 回复

公司 QQ 用不了

gkd 回复

怎么连接可以发一下吗?我根据帖子连接还是失败😭

gkd 回复

能不能详细讲下如果我想用 scrcpy 替换 ATX 的 minicap 需要如何操作,或者有没有相关的文章推荐下也可以,谢谢了

gkd 回复

谢谢,连接成功💯

Kyle 回复

这篇楼主已经讲的很详细了,而且替换起来工作量是最小的

793136254 回复

你好,请问你这个 touch 模块移植完成没

郭军 回复

没有,最近在忙别的

可以把编译好的文件直接放到项目里吗,我折腾了很久在 windows 上面也没能编译成功

touch 移植完成,再次感谢楼主,少走了很多弯路

这个怎么适配 ios 系统?

问下,当我把 scrcpy 视频流转到可接收的 minicap 图片,发现有概率性的,截出来的图是灰色的,也就是图片显示异常,灰色的一个背景图~不知道是否有遇到

chenyouan 回复

你看那个灰色的图片是不是只有几 byte

gkd 回复

是的,类似没有 rgb 的过滤,这个要如何处理。我做自动化的时候,步骤都要截图,然后就发现回传的图片是灰屏的

86楼 已删除
匿名 #87 · 2021年04月08日

请问下按照大佬的步骤执行 ,最后运行 adb shell LD_LIBRARY_PATH=/oem/lib64:/system/lib64:/vendor/lib64:/data/local/tmp CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server
返回下面这个
[server] INFO: Options frame rate: 24 (1 ~ 60)
[server] INFO: Options quality: 60 (1 ~ 100)
[server] INFO: Options projection: 480 (1080, 720, 480, 360...)
[server] INFO: Options control only: false (true / false)
然后运行 node app.js,只返回 listen on 9002
两个运行中的窗口都没有打印收到了图片的日志,是哪步操作有什么问题么,127.0.0.1:9002 网页打不开,ws:/127.0.0.1:1717 也连不上

端口 转发了 吗

匿名 #89 · 2021年04月08日
哲豪 回复

启动后 scrcpy-server.jar 后,运行的这个命令 adb forward tcp:1717 tcp:6612

试试 再 重启下 jar ,然后在刷新页面

匿名 #91 · 2021年04月08日
哲豪 回复

一顿修改之后网页上能看到空白红框,但每次刷新都会 [server] INFO: alive: false,然后就 scrcpy 的进程就断开了😭 😭
[server] INFO: videoChannel: java.nio.channels.SocketChannel[connected local=/127.0.0.1:6612 remote=/127.0.0.1:41711]
[server] WARN: Unknown event type: 71
[server] INFO: realWidth: 1080, realHeight: 1920, desiredWidth: 480, desiredHeight: 856
[server] INFO: banner
{
version: 1
size: 24
real width: 1080
real height: 1920
desired width: 480
desired height: 856
orientation: 0
quirks: 2
}

[server] INFO: realWidth: 1080, realHeight: 1920, desiredWidth: 480, desiredHeight: 856
[server] INFO: hander message: { when=-1ms what=1 obj=rotation target=com.genymobile.scrcpy.ScreenEncoder$1 }
[server] INFO: hander message: { when=-1ms what=1 obj=image target=com.genymobile.scrcpy.ScreenEncoder$1 }
[server] INFO: alive: false

目前看上去是通的,app.js 修改了吗

匿名 #93 · 2021年04月08日
哲豪 回复

😭 😊 哇,谢谢大佬,调了两天了,刚刚突然调通了

你好,楼主,touch 部分的没明白是在哪里处理的,感谢楼主的文章

gkd 回复

你好,可以大致讲下 touch 部分如何集成的吗,非常感谢

手机:realme V5
安卓版本:10
color os : v7
问题:本手机启动后抛出异常,不能正常使用
启动指令:

/data/local/tmp $ LD_LIBRARY_PATH=/system/lib64:/system/product/lib64:/oppo_product/lib64:/data/local/tmp CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server -r 24 -P 480 -Q 60

异常信息:

[server] INFO: Options frame rate: 24 (1 ~ 60)
[server] INFO: Options quality: 60 (1 ~ 100)
[server] INFO: Options projection: 480 (1080, 720, 480, 360...)
[server] INFO: Options control only: false (true / false)
[server] INFO: videoChannel: java.nio.channels.SocketChannel[connected local=/127.0.0.1:6612 remote=/127.0.0.1:47845]
[server] INFO: realWidth: 1080, realHeight: 2400, desiredWidth: 480, desiredHeight: 1064
[server] INFO: banner
{
    version: 1
    size: 24
    real width: 1080
    real height: 2400
    desired width: 480
    desired height: 1064
    orientation: 0
    quirks: 2
}

[server] INFO: realWidth: 1080, realHeight: 2400, desiredWidth: 480, desiredHeight: 1064
[server] ERROR: Exception on thread Thread[ScrcpyImageReaderHandlerThread,5,main]
java.lang.UnsatisfiedLinkError: No implementation found for byte[] com.genymobile.scrcpy.JpegEncoder.compress(java.nio.ByteBuffer, int, int, int, int) (tried Java_com_genymobile_scrcpy_JpegEncoder_compress and Java_com_genymobile_scrcpy_JpegEncoder_compress__Ljava_nio_ByteBuffer_2IIII)
        at com.genymobile.scrcpy.JpegEncoder.compress(Native Method)
        at com.genymobile.scrcpy.ScreenEncoder$ImageAvailableListenerImpl.onImageAvailable(ScreenEncoder.java:179)
        at android.media.ImageReader$ListenerHandler.handleMessage(ImageReader.java:798)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:238)
        at android.os.HandlerThread.run(HandlerThread.java:67)

有大佬知道原因么

感谢大佬的分享,还有两个问题想请教下
1、关于 touch 部分 “后端提供 json 格式接口” 这里,我不会 go,只大概看出来是定义了 MessageType,在接收到前端通过 websocket 发送的请求后对消息拼装再转为 byte,然后发给 Scrcpy-server 么,是跟图片模块用的同一个 socket 么。这里不经过后端处理,直接在前端完成拼接和转换然后通过 websocket 发送到对应设备的 Scrcpy-server 可以么
2、还有现在网页刷新之后,连接就会断开,需要重启 Scrcpy-server.jar 这个要怎么解决呢,每次主动去重启一次?

daming 回复

libcompress.so 有没有放入/data/local/tmp

zm 回复

1、一个 socket connect 两次,第一个是视频流,第二个是控制流
2、可以修改下 accept 的逻辑,可以不用重启
...
while (true) {
LocalSocket videoSocket = localServerSocket.accept();
...
}

自己根据 minicap 官方的 minicap-experimental(kotlin 版本)
https://github.com/DeviceFarmer/minicap/tree/master/experimental
——这个的兼容性简直可以忽略使用,用来借用思路不错
写了一个基于 ImageReader 的方案,居然和楼主的代码异常相似

这里面有一些巨坑,提醒下用来做群控的各位同鞋:
1、部分手机会出现慢推帧的情况,让人感觉卡顿不实时(例如:红米 note8 pro)
ImageReader 中有个 maxImages 设置是配对使用的
模式一:
ImageReader.newInstance(..., maxImages = 2)
imageReader.acquireLatestImage()
模式二:
ImageReader.newInstance(..., maxImages = 1)
imageReader.acquireNextImage()
换模式二时图像就会实时了
2、部分手机会出现翻转会出现:BufferQueue has been abandoned
——在翻转的时候 sleep 下可解决
3、如果想刷新页面而不关闭应用,注意部分手机 handler 会异常
...
while (true) {
LocalSocket videoSocket = localServerSocket.accept();
...
}
搞了很久解决了乱七八糟的问题,将近 200 台设备兼容性不易呀,后续有空做个专门的话题记录下过程

LgdCh 回复

楼主的原理是复用 scrcpy 的外壳写了个 ImageReder 推图片流的方案,touch 还是用的 scrcpy 的方案:基于安卓 android.hardware.input.IInputManager$Stub 的 asInterface(android.os.IBinder) 获取 android.os.IInterface 的 manager,然后调用 injectInputEvent 方法

阿飞 回复

多谢了兄弟👍

chenyouan 回复

因为那个是屏幕旋转角度信息,不是图片,过滤掉就行了

LgdCh 回复

touch 主要是数组协议拼接,直接看 scrcpy 接收的地方,再拼协议就行了
下面这个是别人总结的
按压:

[2,0,0,0,0,0,0,0,0,0,
x >> 24,x << 8 >> 24,x << 16 >> 24,x << 24 >> 24,
y >> 24,y << 8 >> 24,y << 16 >> 24,y << 24 >> 24,1080 >> 8,1080 << 8 >> 8,2280 >> 8,2280 << 8 >> 8,0,0,0,0,0,0]
抬起:

[2,1,0,0,0,0,0,0,0,0,
x >> 24,x << 8 >> 24,x << 16 >> 24,x << 24 >> 24,
y >> 24,y << 8 >> 24,y << 16 >> 24,y << 24 >> 24,1080 >> 8,1080 << 8 >> 8,2280 >> 8,2280 << 8 >> 8,0,0,0,0,0,0]
移动:

[2,2,0,0,0,0,0,0,0,0,
x >> 24,x << 8 >> 24,x << 16 >> 24,x << 24 >> 24,
y >> 24,y << 8 >> 24,y << 16 >> 24,y << 24 >> 24,1080 >> 8,1080 << 8 >> 8,2280 >> 8,2280 << 8 >> 8,0,0,0,0,0,0]

阿飞 回复
mImageReader = ImageReader.newInstance(videoRect.width(), videoRect.height(), PixelFormat.RGBA_8888, mode)

丢帧的问题在于设置格式(PixelFormat.RGBA_8888)问题是否和设备匹配,不知道大佬有没有办法解决,根据设备支持设置格式

郭军 回复

修改作者 ImageReader 的初始化代码:
ImageReader.newInstance(
unlockedVideoSize().getWidth(),
unlockedVideoSize().getHeight(),
PixelFormat.RGBA_8888,
1
);
将 2 改成 1 可以解决帧率卡顿问题,副作用是要损失一点性能

使用-D 获取 DumpHierarchy 的时候,经常推流没返回 hierarchy,这种情况使用 adb 命令的时候也返回 aborted,请问大佬有办法解决么。

貌似安卓 11 和 android12 不行

chenyouan 回复

需要修改 createDisplay 方法,获取的 SDK_INT 不一样。
boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S".equals(Build.VERSION.CODENAME));
return SurfaceControl.createDisplay("scrcpy", secure);

还有个问题想请问下大佬,已经把 libturbojpeg.so 和 libcompress.so 推送到手机的/data/local/tmp 下,手机对应的 so 版本是对的,但是启动 scrcpy 还是有如下报错,机型是 REDMI Note 8 Pro Android 10,启动命令 LD_LIBRARY_PATH=/system/lib64:/system/product/lib64:/data/local/tmp CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server -Q 60 -r 24 -P 480 -D

08-27 17:11:39.342 28274 28573 E scrcpy : Exception on thread Thread[ScrcpyImageReaderHandlerThread,5,main]
08-27 17:11:39.342 28274 28573 E scrcpy : java.lang.UnsatisfiedLinkError: No implementation found for byte[] com.genymobile.scrcpy.JpegEncoder.compress(java.nio.ByteBuffer, int, int, int, int) (tried Java_com_genymobile_scrcpy_JpegEncoder_compress and Java_com_genymobile_scrcpy_JpegEncoder_compress__Ljava_nio_ByteBuffer_2IIII)
08-27 17:11:39.342 28274 28573 E scrcpy : at com.genymobile.scrcpy.JpegEncoder.compress(Native Method)
08-27 17:11:39.342 28274 28573 E scrcpy : at com.genymobile.scrcpy.ScreenEncoder$ImageAvailableListenerImpl.onImageAvailable(ScreenEncoder.java:179)
08-27 17:11:39.342 28274 28573 E scrcpy : at android.media.ImageReader$ListenerHandler.handleMessage(ImageReader.java:798)
08-27 17:11:39.342 28274 28573 E scrcpy : at android.os.Handler.dispatchMessage(Handler.java:107)
08-27 17:11:39.342 28274 28573 E scrcpy : at android.os.Looper.loop(Looper.java:224)
08-27 17:11:39.342 28274 28573 E scrcpy : at android.os.HandlerThread.run(HandlerThread.java:67)

哲豪 回复

这需要做判断吗?还是只要用这个就可以了

chenyouan 回复

直接复制 粘贴我发的,就可以了

哲豪 回复

原本方法是有带参数的,直接用这个会有问题。这个是 1.18scrcpy 里面的代码
public static IBinder createDisplay(String name, boolean secure)
这个是旧的

chenyouan 回复


这样有问题?

哲豪 回复

Build.VERSION_CODES.R 这个有问题,我直接改为 30

chenyouan 回复

那应该是你的编译版本低于 30 所以用不了

编译使用还是提示图片加载中,很尴尬,看了图片,就 0kb,不知道是不是 app.js 截图不兼容 android11 和 android12,我试了 android10 正常。

群主有没有用目前最新的 scrcpy1.18 进行改造,是否有区别~



大家有没遇到这种情况:
程序启动后 浏览器(http://127.0.0.1:9002/ )没有画面,只有红色框框(输过 adb forward ),刷新后,还是没画面,但控制台就有数据输出了
再次刷新,接收 scrcpy 的控制台程序就停止了

121楼 已删除


修改 2 的 frameBody 是不是缺少实例化?

123楼 已删除
南山老人i [该话题已被删除] 中提及了此贴 01月13日 16:26


在使用 mumu 模拟器进行验证时,出现了格式不正确的问题
ERROR: onImageAvailable: The producer output buffer format 0x3 doesn't match the ImageReader's configured buffer format 0x1.
是否有好的解决方案 可以通过配置初始化解析器

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册