STF 基于 Scrcpy 的远程调试方案

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

基于 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 资源占用怎么样

大浪 #4 回复

没有用 STF,没法帮你了

codeskyblue #7 回复

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

wenxiaomao #9 回复

只要不比 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 #13 回复

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

zerkzzzz #14 回复

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

wenxiaomao #17 回复

编译完成,启动 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 #19 回复

图片不清晰请尝试将启动命名修改为如下,主要是修改这 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 #9 回复

可以加您微信吗 大神

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

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

大浪 #4 回复

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

qchuang #26 回复

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

大浪 #27 回复

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

wpx #24 回复

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

Zayoi #25 回复

可以,按照控制消息的格式,向 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 #38 回复

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

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

Jacc #40 回复

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

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

qchuang #28 回复

大胸弟,怎样了 成功了没

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

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

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


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

wenxiaomao #41 回复

现在 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 #63 回复

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

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

郭军 #64 回复

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

Kyle #65 回复

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

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

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

gkd #66 回复

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

郭军 #71 回复

socket 需要连接两次

Kyle #70 回复

公司 QQ 用不了

gkd #72 回复

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

gkd #67 回复

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

gkd #72 回复

谢谢,连接成功💯

Kyle #75 回复

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

793136254 #63 回复

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

郭军 #78 回复

没有,最近在忙别的

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

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

这个怎么适配 ios 系统?

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

chenyouan #83 回复

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

gkd #84 回复

是的,类似没有 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日
哲豪 #88 回复

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

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

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

一顿修改之后网页上能看到空白红框,但每次刷新都会 [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日
哲豪 #92 回复

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

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

gkd #81 回复

你好,可以大致讲下 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 #97 回复

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

zm #98 回复

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 #96 回复

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

阿飞 #102 回复

多谢了兄弟👍

chenyouan #85 回复

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

LgdCh #96 回复

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]

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

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

郭军 #106 回复

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

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

貌似安卓 11 和 android12 不行

chenyouan #109 回复

需要修改 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)

哲豪 #110 回复

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

chenyouan #112 回复

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

哲豪 #113 回复

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

chenyouan #114 回复


这样有问题?

哲豪 #115 回复

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

chenyouan #116 回复

那应该是你的编译版本低于 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.
是否有好的解决方案 可以通过配置初始化解析器

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