手机投屏到电脑(adb加python)

注:这是本人用adb连接手机用python写的图像界面,使用前请先用adb连接好要控制的手机如果不知道怎么连接的可以看我这篇文章电脑adb连接模拟器,本项目以上传到本人Github仓库,但因为是python的原因可能会有一点延迟。

Github仓库地址:suyihang15/ADB-link-tool: 用于有电脑上用ADB连接手机的需求的人,做了简单的图形化界面

1、环境准备

python

2、源码介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import subprocess
import cv2
import numpy as np
import tkinter as tk
from PIL import Image, ImageTk
import threading
import time

# 改成你自己的 adb 路径
ADB_PATH = r"\adb.exe" #填你下载的adb.exe文件的地址,不然后面的都不行的,记住
WIN_W = 480
WIN_H = 950

class PhoneControl:
def __init__(self, root):
self.root = root
self.root.title("手机联机软件(作者Mr.苏)")
self.root.geometry(f"{WIN_W}x{WIN_H}")

# 1. 投屏显示区域:用于显示手机画面
self.screen = tk.Label(root)
self.screen.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.screen.bind("<Button-1>", self.click) # 绑定鼠标点击事件

# 2. 功能按键区:返回、主页、唤醒等
btn_frame = tk.Frame(root)
btn_frame.pack(pady=5)
tk.Button(btn_frame, text="返回", command=self.back).grid(row=0, column=0, padx=3)
tk.Button(btn_frame, text="主页", command=self.home).grid(row=0, column=1, padx=3)
tk.Button(btn_frame, text="唤醒", command=self.wake).grid(row=0, column=2, padx=3)
tk.Button(btn_frame, text="电源", command=self.power).grid(row=0, column=3, padx=3)
tk.Button(btn_frame, text="截图", command=self.screenshot).grid(row=0, column=4, padx=3)

# 3. ADB 命令终端区:输入自定义命令
term_frame = tk.Frame(root)
term_frame.pack(fill=tk.X, padx=10, pady=5)
tk.Label(term_frame, text="ADB命令:").pack(side=tk.LEFT)
self.cmd_input = tk.Entry(term_frame)
self.cmd_input.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
tk.Button(term_frame, text="执行", command=self.run_cmd).pack(side=tk.LEFT)

# 命令输出框:显示命令执行结果
self.output_box = tk.Text(root, height=6)
self.output_box.pack(fill=tk.BOTH, padx=10, pady=5)

self.running = True
self.current_frame = None

# 启动投屏线程
threading.Thread(target=self.stream, daemon=True).start()
# 启动声音转发线程
threading.Thread(target=self.audio, daemon=True).start()
# 启动界面刷新循环
self.update_ui()

# ---------------------- 核心功能模块 ----------------------
# 投屏流获取:不断从手机抓取屏幕数据(核心循环)
def stream(self):
while self.running:
try:
# 使用 ADB exec-out 命令抓取手机屏幕二进制流
data = subprocess.run([ADB_PATH, "exec-out", "screencap", "-p"], stdout=subprocess.PIPE).stdout
# 数据过短表示抓取失败,跳过
if len(data) < 1000:
continue
# 将二进制流转为 numpy 数组
nparr = np.frombuffer(data, np.uint8)
# 解码为图片
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
# 转换颜色格式 (BGR -> RGB) 并赋值给当前帧变量
if img is not None:
self.current_frame = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
except:
pass
# 控制抓取频率,0.008秒 = 125帧左右
time.sleep(0.008)

# UI 画面刷新:将 current_frame 显示在 Label 上
def update_ui(self):
if self.current_frame is not None:
try:
h, w = self.current_frame.shape[:2]
# 计算缩放比例,保证画面适应窗口且不变形
scale = min(WIN_W/w, 850/h)
img = cv2.resize(self.current_frame, (int(w*scale), int(h*scale)))
# 转为 Tkinter 可用的图片格式
imgtk = ImageTk.PhotoImage(Image.fromarray(img))
# 更新界面显示
self.screen.config(image=imgtk)
self.screen.imgtk = imgtk # 强制保存引用,防止被垃圾回收
except:
pass
# 递归调用,实现每 8 毫秒刷新一次界面
if self.running:
self.root.after(8, self.update_ui)

# 鼠标点击控制:将电脑鼠标坐标映射为手机坐标并发送点击指令
def click(self, e):
if self.current_frame is None:
return
h, w = self.current_frame.shape[:2]
# 计算真实的手机点击位置
x = int(e.x * w / WIN_W)
y = int(e.y * h / 850)
# 执行 ADB 点击命令
subprocess.Popen([ADB_PATH, "shell", "input", "tap", str(x), str(y)])

# 音频转发:通过 screenrecord 转发手机声音流
def audio(self):
while self.running:
try:
# 启动手机录屏并输出到标准输出(用于转发声音)
subprocess.Popen([ADB_PATH, "shell", "screenrecord", "--output-format", "h264", "-"],
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
except:
pass
time.sleep(1)

# ---------------------- 基础按键功能模块 ----------------------
# 模拟手机返回键
def back(self):
subprocess.Popen([ADB_PATH, "shell", "input", "keyevent", "4"])
# 模拟手机主页键
def home(self):
subprocess.Popen([ADB_PATH, "shell", "input", "keyevent", "3"])
# 模拟多任务键
def recent(self):
subprocess.Popen([ADB_PATH, "shell", "input", "keyevent", "187"])
# 模拟唤醒屏幕/点亮屏幕
def wake(self):
subprocess.Popen([ADB_PATH, "shell", "input", "keyevent", "224"])
# 模拟电源键(锁屏/点亮)
def power(self):
subprocess.Popen([ADB_PATH, "shell", "input", "keyevent", "26"])

# 截图功能:截取手机屏幕并保存到电脑
def screenshot(self):
try:
# 先在手机内存截图
subprocess.run([ADB_PATH, "shell", "screencap", "/sdcard/screenshot.png"])
# 拉取到电脑当前目录
subprocess.run([ADB_PATH, "pull", "/sdcard/screenshot.png", "screenshot.png"])
self.output_box.insert(tk.END, "\n截图已保存到当前目录:screenshot.png")
except:
self.output_box.insert(tk.END, "\n截图失败")

# ADB 命令执行:读取输入框的命令并执行
def run_cmd(self):
cmd = self.cmd_input.get().strip()
if not cmd:
return
try:
# 拆分命令并执行
res = subprocess.run([ADB_PATH] + cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# 输出结果
self.output_box.insert(tk.END, f"\n> {cmd}\n{res.stdout}{res.stderr}")
self.output_box.see(tk.END) # 滚动到最底部
except Exception as e:
self.output_box.insert(tk.END, f"\n错误:{str(e)}")

if __name__ == "__main__":
root = tk.Tk()
app = PhoneControl(root)
root.mainloop()

3、运行

(1)安装所需第三方库 pip install opencv-python numpy pillow

(2)运行python文件

4、效果展示

这些就是全部功能,如果想直接使用就直接用我打包好的exe文件就行。

5、打包

因为本身功能不算多,打包用pyinstaller这个,后面会再优化以下,可以自行打包试试记得要用到adb.exe哦,要把他一起打包才有用哦,因为本身是基于它的。