使用PyQt5和ADB、Minicap 实现雷电模拟器中QQ应用的实时屏幕显示,并支持点击等交互操作

雷电模拟器中QQ应用的实时屏幕显示与交互控制

下面是一个完整的解决方案,使用PyQt5和ADB实现雷电模拟器中QQ应用的实时屏幕显示,并支持点击等交互操作。

方案概述

这个方案将结合以下技术:

  1. Minicap - 用于高效获取安卓设备屏幕实时流
  2. ADB - 用于与雷电模拟器交互
  3. PyQt5 - 创建GUI界面并显示实时屏幕
  4. Minitouch - 用于模拟触摸操作(可选)

环境准备

1. 安装必要软件

  • 雷电模拟器(建议最新版)
  • Python 3.8+
  • PyQt5: pip install PyQt5
  • OpenCV: pip install opencv-python
  • NumPy: pip install numpy

2. 配置雷电模拟器

  1. 打开雷电模拟器设置
  2. 进入"其他设置",开启"Root权限"和"ADB调试"
  3. 记下ADB端口号(默认为5555)

3. 准备Minicap工具

  1. 下载适合雷电模拟器Android版本的Minicap
  2. 将minicap和minicap.so推送到模拟器中:
adb push minicap /data/local/tmp/
adb push minicap.so /data/local/tmp/
adb shell chmod 777 /data/local/tmp/minicap*

完整代码实现

import sys
import socket
import struct
import numpy as np
import cv2
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from threading import Thread
import subprocess
import time

class MinicapStream(QThread):
    """Minicap视频流线程"""
    new_frame = pyqtSignal(np.ndarray)
    
    def __init__(self, host='127.0.0.1', port=1313):
        super().__init__()
        self.host = host
        self.port = port
        self.running = False
        self.banner = {
            'version': 0, 'length': 0, 'pid': 0,
            'realWidth': 0, 'realHeight': 0,
            'virtualWidth': 0, 'virtualHeight': 0,
            'orientation': 0, 'quirks': 0
        }
    
    def run(self):
        """启动Minicap流"""
        self.running = True
        
        # 启动minicap服务
        self.start_minicap_server()
        
        # 连接到minicap服务
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            sock.connect((self.host, self.port))
        except socket.error as e:
            print(f"连接Minicap失败: {e}")
            return
            
        # 读取banner信息
        banner = sock.recv(24)
        self.parse_banner(banner)
        
        # 持续读取帧数据
        read_frame_bytes = 0
        frame_body_length = 0
        data = b''
        
        while self.running:
            try:
                chunk = sock.recv(4096)
            except socket.error as e:
                print(f"接收数据错误: {e}")
                break
                
            cursor = 0
            buf_len = len(chunk)
            
            while cursor < buf_len:
                if read_frame_bytes < 4:
                    frame_body_length += (chunk[cursor] << (read_frame_bytes * 8)) >> 0
                    cursor += 1
                    read_frame_bytes += 1
                else:
                    if buf_len - cursor >= frame_body_length:
                        data += chunk[cursor:cursor + frame_body_length]
                        self.process_frame(data)
                        cursor += frame_body_length
                        frame_body_length = read_frame_bytes = 0
                        data = b''
                    else:
                        data += chunk[cursor:buf_len]
                        frame_body_length -= buf_len - cursor
                        read_frame_bytes += buf_len - cursor
                        cursor = buf_len
        
        sock.close()
    
    def start_minicap_server(self):
        """启动minicap服务"""
        # 获取设备分辨率
        size_output = subprocess.check_output(['adb', 'shell', 'wm', 'size']).decode()
        size = size_output.split()[-1].split('x')
        width, height = int(size[0]), int(size[1])
        
        # 转发端口
        subprocess.run(['adb', 'forward', 'tcp:1313', 'localabstract:minicap'])
        
        # 启动minicap
        subprocess.Popen([
            'adb', 'shell',
            'LD_LIBRARY_PATH=/data/local/tmp',
            '/data/local/tmp/minicap',
            '-P', f'{width}x{height}@{width}x{height}/0',
            '-S', '-Q', '50'
        ])
    
    def parse_banner(self, banner):
        """解析minicap banner信息"""
        (self.banner['version'],
         self.banner['length'],
         self.banner['pid'],
         self.banner['realWidth'],
         self.banner['realHeight'],
         self.banner['virtualWidth'],
         self.banner['virtualHeight'],
         self.banner['orientation'],
         self.banner['quirks']) = struct.unpack("<2b5ibB", banner)
    
    def process_frame(self, data):
        """处理帧数据并发出信号"""
        try:
            # 将JPEG数据转换为numpy数组
            nparr = np.frombuffer(data, np.uint8)
            img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
            
            if img is not None:
                self.new_frame.emit(img)
        except Exception as e:
            print(f"处理帧错误: {e}")

class TouchController:
    """触摸控制器"""
    def __init__(self):
        # 检查并启动minitouch服务
        self.setup_minitouch()
    
    def setup_minitouch(self):
        """设置minitouch服务"""
        # 这里可以添加minitouch的安装和启动代码
        # 实际项目中可能需要将minitouch推送到设备并启动
        pass
    
    def tap(self, x, y):
        """模拟点击"""
        subprocess.run(['adb', 'shell', 'input', 'tap', str(x), str(y)])
    
    def swipe(self, x1, y1, x2, y2, duration=300):
        """模拟滑动"""
        subprocess.run(['adb', 'shell', 'input', 'swipe', str(x1), str(y1), str(x2), str(y2), str(duration)])

class MainWindow(QWidget):
    """主窗口"""
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.init_minicap()
        self.touch_controller = TouchController()
        
        # 鼠标事件相关变量
        self.last_pos = None
        self.scale_factor = 1.0
    
    def init_ui(self):
        """初始化UI"""
        self.setWindowTitle('雷电模拟器QQ控制')
        self.setGeometry(100, 100, 800, 600)
        
        layout = QVBoxLayout()
        self.setLayout(layout)
        
        self.label = QLabel(self)
        self.label.setAlignment(Qt.AlignCenter)
        layout.addWidget(self.label)
        
        # 启用鼠标跟踪
        self.setMouseTracking(True)
        self.label.setMouseTracking(True)
    
    def init_minicap(self):
        """初始化Minicap流"""
        self.minicap = MinicapStream()
        self.minicap.new_frame.connect(self.update_frame)
        self.minicap.start()
    
    def update_frame(self, frame):
        """更新显示帧"""
        # 转换颜色空间(BGR->RGB)
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        
        # 转换为QImage
        h, w, ch = frame.shape
        bytes_per_line = ch * w
        q_img = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
        
        # 缩放图像以适应窗口
        scaled_pixmap = QPixmap.fromImage(q_img).scaled(
            self.label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
        )
        
        # 更新显示
        self.label.setPixmap(scaled_pixmap)
        
        # 计算缩放因子
        self.scale_factor = w / scaled_pixmap.width()
    
    def mousePressEvent(self, event):
        """鼠标点击事件"""
        if event.button() == Qt.LeftButton:
            # 获取点击位置并转换为设备坐标
            label_pos = self.label.mapFrom(self, event.pos())
            pixmap = self.label.pixmap()
            
            if pixmap:
                # 计算在图像上的相对位置
                img_x = label_pos.x() - (self.label.width() - pixmap.width()) / 2
                img_y = label_pos.y() - (self.label.height() - pixmap.height()) / 2
                
                if 0 <= img_x < pixmap.width() and 0 <= img_y < pixmap.height():
                    # 转换为设备实际坐标
                    device_x = int(img_x * self.scale_factor)
                    device_y = int(img_y * self.scale_factor)
                    
                    # 执行点击操作
                    self.touch_controller.tap(device_x, device_y)
                    self.last_pos = (device_x, device_y)
    
    def mouseMoveEvent(self, event):
        """鼠标移动事件"""
        if event.buttons() & Qt.LeftButton and self.last_pos:
            # 处理拖动操作
            label_pos = self.label.mapFrom(self, event.pos())
            pixmap = self.label.pixmap()
            
            if pixmap:
                img_x = label_pos.x() - (self.label.width() - pixmap.width()) / 2
                img_y = label_pos.y() - (self.label.height() - pixmap.height()) / 2
                
                if 0 <= img_x < pixmap.width() and 0 <= img_y < pixmap.height():
                    device_x = int(img_x * self.scale_factor)
                    device_y = int(img_y * self.scale_factor)
                    
                    # 执行滑动操作
                    self.touch_controller.swipe(
                        self.last_pos[0], self.last_pos[1],
                        device_x, device_y
                    )
                    self.last_pos = (device_x, device_y)
    
    def mouseReleaseEvent(self, event):
        """鼠标释放事件"""
        if event.button() == Qt.LeftButton:
            self.last_pos = None
    
    def closeEvent(self, event):
        """窗口关闭事件"""
        self.minicap.running = False
        self.minicap.wait()
        event.accept()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    
    # 确保ADB服务器已启动
    subprocess.run(['adb', 'start-server'])
    
    # 连接雷电模拟器
    try:
        subprocess.run(['adb', 'connect', '127.0.0.1:5555'], check=True)
    except subprocess.CalledProcessError:
        print("无法连接到雷电模拟器,请确保模拟器已运行且ADB调试已开启")
        sys.exit(1)
    
    # 启动QQ应用
    qq_package = "com.tencent.mobileqq"
    qq_activity = "com.tencent.mobileqq.activity.SplashActivity"
    subprocess.run(['adb', 'shell', 'am', 'start', '-n', f'{qq_package}/{qq_activity}'])
    
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

功能说明

1. 实时屏幕显示

  • 使用Minicap获取雷电模拟器的实时屏幕流
  • 通过socket接收Minicap数据并解析帧
  • 将JPEG图像转换为Qt可显示的格式并更新UI

2. 交互控制

  • 通过ADB的input tapinput swipe命令模拟触摸操作
  • 计算鼠标位置与实际屏幕坐标的转换关系
  • 支持点击和拖动操作

3. 自动启动QQ

  • 通过ADB命令自动启动QQ应用
  • 使用QQ的包名和主Activity启动应用

优化建议

  1. 性能优化

    • 使用多线程处理图像解码
    • 调整Minicap的帧率和质量参数(-Q选项)
    • 考虑使用硬件加速的图像处理
  2. 功能扩展

    • 添加键盘控制支持
    • 实现多点触控(需要Minitouch支持)
    • 添加截图和录制功能
  3. 稳定性改进

    • 添加ADB连接状态监控
    • 实现自动重连机制
    • 处理Minicap服务崩溃的情况

常见问题解决

  1. Minicap无法启动

    • 检查设备架构和Android版本是否匹配
    • 确保minicap和minicap.so有执行权限
    • 尝试不同的SDK版本minicap.so
  2. ADB连接问题

    • 确保雷电模拟器的ADB调试已开启
    • 检查端口是否正确(默认5555)
    • 尝试重启ADB服务
  3. 触摸坐标不准确

    • 检查设备分辨率设置
    • 验证坐标转换计算
    • 考虑使用Minitouch替代ADB input

这个方案提供了完整的实时屏幕显示和交互控制功能,可以根据需要进行进一步定制和扩展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学亮编程手记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值