当前位置: 首页 > news >正文

如何把一台手机的屏幕投到另一台手机上

如何把一台手机的屏幕投到另一台手机上

在PC上,我们可以用Scrcpy将手机的屏幕投屏过来,并且可以直接在PC上操作手机

而用手机控制手机,一般的做法是用一些远程工具(例如RustDesk、向日葵等)传输视频流,然后通过无障碍服务进行模拟操作,这种方式需要目标设备开启无障碍服务。

如果只需要传输视频流而不需要模拟操作,其实可以借助adb和scrcpy来完成,由于scrcpy并没有适配安卓arm64版本,所以我们需要自行对视频流进行解析,下图是这一方案的流程图,注意,这种方案下,控制端需要root权限

image.png

方案难点

  1. 控制端使用ADB
    Scrcpy的视频流传输在本地的localabstract,需要通过adb将此端口转发出来,这在作为控制端的安卓设备上并不容易实现
  2. 被控端ADB授权
    使用OTG线连接两台设备时,可以像PC一样对设备进行手动授权。而使用无线方式连接,需要确保wifi ADB开启,同时也需要授权
  3. 视频流解析
    Scrcpy的视频流基于screenrecord获取,而screenrecord传输的并非是标准协议的视频,而是H264视频裸流,需要自己解析视频数据播放

效果展示

手机投屏手机

流程

被控端

  1. 无感投屏(可选,需要Root)
    正常流程下,需要先用OTG线连接两个设备,然后在弹出的对话框中给控制端ADB调试授权。如果要实现无感,可以将ADB的调试授权校验关闭,同时自动开启wifi adb

    此功能可以用Magisk模块实现,需要装Magisk,如果你此前没有装过,可以(参考教程),然后刷入Wifi ADB模块),此模块只打开了wifi ADB并监听5555端口,你也可以自行编写模块,在模块中添加system.prop,内容如下

persist.sys.usb.config=adb
sys.usb.config=adb
persist.sys.usb.ffbm-02.func=adb
persist.sys.usb.qmmi.func=adb
sys.usb.configfs=1
persist.security.adbinput=1
persist.security.adbinstall=1
persist.security.uks_opened=1
ro.secure=0
ro.adb.secure=0
service.adb.tcp.port=5555

控制端

需要root权限,如果没有先去获取Root

安装ADB

同样通过Magisk模块来安装ADB,(模块地址)

安装完毕后,切换到su用户,可以在命令行下使用abd和fastboot命令

推送并启动Scrcpy-Server

在Scrcpy发布页面下载Scrcpy-Server

image.png

推送并启动

# 推送scrcpy-server到tmp目录
adb push scrcpy-server /data/local/tmp
# 启动scrcpy服务 传输h264裸流
adb shell su -c "CLASSPATH=/data/local/tmp/scrcpy-server app_process / com.genymobile.scrcpy.Server 3.3.1 tunnel_forward=true audio=false control=false cleanup=false raw_stream=true"
# 转发被控端的scrcpy端口到控制端的11234端口
adb forward tcp:11234 localabstract:scrcpy

至此,scrcpy-server已经启动完成并向控制端的11234端口推送视频流

解析并播放视频流

解析视频流并渲染到Surface,Kotlin代码如下

package com.example.scrcpyclientimport android.media.MediaCodec
import android.media.MediaFormat
import android.view.Surface
import java.net.Socket
import java.nio.ByteBuffer
import kotlin.concurrent.threadclass H264StreamDecoder(private val surface: Surface,private val ip: String,private val port: Int
) {private var codec: MediaCodec? = nullprivate var running = falsefun start() {running = truethread {try {val socket = Socket(ip, port)val input = socket.getInputStream()codec = MediaCodec.createDecoderByType("video/avc")val format = MediaFormat.createVideoFormat("video/avc", 1280, 720)codec?.configure(format, surface, null, 0)codec?.start()val buffer = ByteArray(4096)var nalBuffer = ByteArray(0)while (running) {val len = input.read(buffer)if (len <= 0) break// 拼接到 NAL 缓冲区nalBuffer += buffer.copyOf(len)// 解析 NAL 单元 (以 0x00000001 开头)while (true) {val startCodeIndex = nalBuffer.indexOfStartCode()if (startCodeIndex < 0) breakval nextStartCodeIndex = nalBuffer.indexOfStartCode(startCodeIndex + 4)if (nextStartCodeIndex < 0) breakval nal = nalBuffer.copyOfRange(startCodeIndex, nextStartCodeIndex)feedDecoder(nal)nalBuffer = nalBuffer.copyOfRange(nextStartCodeIndex, nalBuffer.size)}}codec?.stop()codec?.release()input.close()socket.close()} catch (e: Exception) {e.printStackTrace()}}}fun stopDecoding() {running = false}private fun feedDecoder(nal: ByteArray) {val codec = codec ?: returnval index = codec.dequeueInputBuffer(10000)if (index >= 0) {val inputBuffer = codec.getInputBuffer(index)inputBuffer?.clear()inputBuffer?.put(nal)codec.queueInputBuffer(index, 0, nal.size, System.nanoTime() / 1000, 0)}val bufferInfo = MediaCodec.BufferInfo()var outIndex = codec.dequeueOutputBuffer(bufferInfo, 10000)while (outIndex >= 0) {codec.releaseOutputBuffer(outIndex, true)outIndex = codec.dequeueOutputBuffer(bufferInfo, 0)}}private fun ByteArray.indexOfStartCode(fromIndex: Int = 0): Int {for (i in fromIndex until size - 4) {if (this[i] == 0x00.toByte() &&this[i + 1] == 0x00.toByte() &&this[i + 2] == 0x00.toByte() &&this[i + 3] == 0x01.toByte()) {return i}}return -1}
}

为什么不用直接screenrecord?

调用screenrecord,可以直接捕获屏幕输出视频流,例如执行以下命令,可以直接调用ffplay播放手机屏幕视频流

adb exec-out screenrecord --output-format=h264 - | ffplay -flags low_delay -framerate 60 -probesize 32 -sync video -an

究其原因,是因为scrcpy封装了除了视频流传输以外的很多视频处理功能,例如视频裁剪、旋转、翻转等,详细命令配置可以(参考这里),并且scrcpy对于screenrecord的配置优化合理,适用于大多数场景。

需要特别指出的时,scrcpy的命令参数不一定和scrcpy-server的启动参数完全一致,例如对于设备旋转翻转,scrcpy使用orientation,而对应的scrcpy-server的启动参数为capture_orientation,,具体的参数可以(查阅scrcpy源码)

http://www.dtcms.com/a/266281.html

相关文章:

  • Perforce QAC 与 Klocwork 重磅升级:质量突破+许可降本
  • 【VScode | 格式化文档】一文掌握VScode使用 clang-format 的文档格式化(C/C++)
  • 文心大模型及百度大模型内容安全平台齐获信通院大模型安全认证
  • 微信小程序如何实现再多个页面共享数据
  • 机器学习中的数学---常用距离计算方法详解
  • 通过 Ansys Discovery CFD 仿真探索电池冷板概念
  • 睿尔曼系列机器人——以创新驱动未来,重塑智能协作新生态(下)
  • 【IM项目笔记】1、WebSocket协议和服务端推送Web方案
  • 在 Ubuntu 22.04 上使用 Minikube 部署 Go 应用到 Kubernetes
  • 商品中心—19.库存分桶高并发的优化文档
  • element-plus按需自动导入的配置 以及icon图标不显示的问题解决
  • Ubuntu 22.04 + MySQL 8 无密码登录问题与 root 密码重置指南
  • ubuntu22桌面版中文输入法 fcitx5
  • goole chrome变更默认搜索引擎为百度
  • MySQL(116)如何监控负载均衡状态?
  • 如何调节笔记本电脑亮度?其实有很多种方式可以调整亮度
  • Linux中容器文件操作和数据卷使用以及目录挂载
  • Oracle CTE递归实现PCB行业的叠层关系
  • 缓存雪崩、穿透、预热、更新与降级问题与实战解决方案
  • 【网络】Linux 内核优化实战 - net.core.flow_limit_table_len
  • 批量剪辑混剪系统源码搭建与定制化开发:支持OEM
  • LeetCode1456. 定长子串中元音的最大数目
  • Acrel-1000系列分布式光伏监控系统在湖北荆门一马光彩大市场屋顶光伏发电项目中应用
  • 在数学中一个实对称矩阵的特性分析
  • 每天一个前端小知识 Day 21 - 浏览器兼容性与 Polyfill 策略
  • Web 项目如何自动化测试?
  • 大语言模型预训练数据——数据采样方法介绍以GPT3为例
  • 银河麒麟V10服务器版 + openGuass + JDK +Tomcat
  • 基于FPGA的一维序列三次样条插值算法verilog实现,包含testbench
  • 类图+案例+代码详解:软件设计模式----原型模式