bug: RawImage performance is still sub‑optimal on my Windows PC. Rendering takes 30‑50 ms, limiting the maximum frame rate of scrcpy to only 30 FPS.
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 42/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Cần làm rõ
- Mức độ hoạt động
- Ít trao đổi
- Công nghệ
- flutter, python
- Lĩnh vực
- frontend, performance
Hướng nghiên cứu
Bắt đầu tại điểm vào ft.RawImage.render được sử dụng trong ví dụ Scrcpy được cung cấp và theo dõi đường dẫn kết xuất trên Windows. Tái hiện các mốc thời gian đã báo cáo, so sánh chúng với các lần kết xuất 30–50 ms được ghi trong log, đồng thời xác minh rằng độ trễ kết xuất và tốc độ khung hình thu được được cải thiện.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Duplicate Check
- I have searched the opened issues and there are no duplicates
Describe the bug
Scrcpy can pull the screen video stream at 120 fps, yet flet rawimage.render takes 30‑50 ms to render, resulting in a maximum display frame rate of only 30 fps.
Code sample
Code
import queue
import os
import time
import struct
from threading import Thread
from adbutils import adb, Network
from av.codec.context import CodecContext
import logging
import flet as ft
from typing import Optional
logging.basicConfig(level=logging.INFO)
logging.getLogger().setLevel(logging.INFO)
logger = logging.getLogger(__name__)
class ScrcpyClient:
rimg=Optional[ft.RawImage]
def __init__(self, serial=None, rimg: Optional[ft.RawImage]=None):
self.rimg = rimg
# import sys
# import io
#
# def pyav_sanity_check():
# # 基础导入
# try:
# import av
# logger.info(f"✅ PyAV 导入成功 | version = {av.__version__}")
# except Exception as e:
# logger.info(f"❌ import av failed: {e}")
# return False
#
# # format
# try:
# from av import format
# logger.info("✅ av.format")
# except Exception as e:
# logger.info(f"❌ av.format: {e}")
#
# # codec
# try:
# from av import codec
# from av.codec import CodecContext
# logger.info("✅ av.codec")
# except Exception as e:
# logger.info(f"❌ av.codec: {e}")
#
# # stream
# try:
# from av import stream
# logger.info("✅ av.stream")
# except Exception as e:
# logger.info(f"❌ av.stream: {e}")
#
# # frame
# try:
# from av.frame import Frame
# from av.video.frame import VideoFrame
# from av.audio.frame import AudioFrame
# # 构造空帧
# vf = VideoFrame(width=320, height=240, format="rgb24")
# af = AudioFrame(format="flt", layout="stereo", samples=512)
# logger.info("✅ Frame / VideoFrame / AudioFrame")
# except Exception as e:
# logger.info(f"❌ Frame: {e}")
#
# # packet
# try:
# from av.packet import Packet
# pkt = Packet(bytes(200))
# logger.info("✅ av.packet.Packet")
# except Exception as e:
# logger.info(f"❌ Packet: {e}")
#
# # container 内存IO
# try:
# mem_buf = io.BytesIO()
# out = av.open(mem_buf, mode="w", format="mp4")
# out.close()
# logger.info("✅ av.container 内存IO容器")
# except Exception as e:
# logger.info(f"⚠️ container mp4 io: {e}")
#
# # 视频编码器
# for c_name in ["h264", "h265", "mpeg4"]:
# try:
# c = av.Codec(c_name, "w")
# logger.info(f"✅ 视频编码器 {c_name}: {c.name}")
# except Exception as e:
# logger.info(f"⚠️ 视频编码器 {c_name} 不可用: {e}")
#
# # 音频编码器
# for c_name in ["aac", "mp3", "opus"]:
# try:
# c = av.Codec(c_name, "w")
# logger.info(f"✅ 音频编码器 {c_name}: {c.name}")
# except Exception as e:
# logger.info(f"⚠️ 音频编码器 {c_name} 不可用: {e}")
#
# logger.info("\n=== 测试结束 ===")
# logger.info("提示:出现⚠️只是缺少ffmpeg组件;出现❌代表PyAV库损坏,需要重装")
# return True
#
# pyav_sanity_check()
# 1. 连接设备
devices = adb.device_list()
if not devices:
raise RuntimeError("未检测到任何 ADB 设备!")
self.device = adb.device(serial=serial) if serial else devices[0]
logger.info(f"[+] 连接设备: {self.device.serial}")
self.video_socket = None
self.device_name = ""
self.codec_id = ""
self.resolution = (0, 0)
self.frame_queue = queue.Queue(maxsize=1) # 只保留最新帧,杜绝积压延迟
self.running = False
def _recv_exact(self, n_bytes):
"""保证精准读取 n 字节,避免网络分包导致解包失败"""
data = bytearray()
while len(data) < n_bytes:
packet = self.video_socket.recv(n_bytes - len(data))
if not packet:
# 链路断开,返回 None 退出
return None
data.extend(packet)
return bytes(data)
def start_server(self):
# 清理历史进程
self.device.shell("pkill -f com.genymobile.scrcpy.Server || true")
time.sleep(0.3)
# 推送服务 jar 包
server_path = "/data/local/tmp/scrcpy-server.jar"
print(os.getcwd())
self.device.sync.push("assets/scrcpy-server.jar", server_path)
# 注意:SERVER_VERSION 请确保与你下载的 jar 包版本严格匹配(如 2.4 / 2.7 / 3.0 等)
SERVER_VERSION = "4.1"
args = [
f"CLASSPATH={server_path}",
"app_process",
"/",
"com.genymobile.scrcpy.Server",
SERVER_VERSION,
"scid=-1",
"log_level=info",
"video=true",
"audio=false",
"control=false",
"video_codec=h264",
"max_fps=120", # 最高 120 帧采集
"video_bit_rate=20000000", # 20Mbps 码率,减少画质损失
"i_frame_interval=1", # 每秒一个关键帧,加速解码
"tunnel_forward=true",
"send_frame_meta=false" # 输出原生 H.264 裸流
]
logger.info("[+] 启动 scrcpy-server...")
self.server_process = self.device.shell(args, stream=True)
# 尝试建立 Socket 连接
start_time = time.time()
while time.time() - start_time < 5:
try:
self.video_socket = self.device.create_connection(Network.LOCAL_ABSTRACT, "scrcpy")
break
except Exception:
time.sleep(0.2)
if not self.video_socket:
raise ConnectionError("无法连接到 scrcpy-server Socket!")
logger.info("[+] Video Socket 连接成功!开始解析 Header...")
self._parse_header()
def _parse_header(self):
# 1. Dummy Byte
dummy = self._recv_exact(1)
if dummy != b"\x00":
raise ValueError(f"Header 校验异常: Dummy byte = {dummy}")
# 2. 设备名 (64 字节)
raw_name = self._recv_exact(64)
self.device_name = raw_name.decode('utf-8', errors='ignore').rstrip('\x00')
# 3. 编码器 FourCC (4 字节,如 'h264')
raw_codec = self._recv_exact(4)
self.codec_id = raw_codec.decode('utf-8', errors='ignore')
# 4. 分辨率 (8 字节: 宽度 4B + 高度 4B)
raw_res = self._recv_exact(8)
w, h = struct.unpack(">II", raw_res)
self.resolution = (w & 0x7FFFFFFF, h & 0x7FFFFFFF)
logger.info(f"|--- 设备名: {self.device_name}")
logger.info(f"|--- 编码器: {self.codec_id}")
logger.info(f"|--- 分辨率: {self.resolution[0]}x{self.resolution[1]}")
def decode_loop(self):
"""【后台线程】只负责从网络接收数据并解码,解码后存入 queue,绝对不操作 GUI"""
codec = CodecContext.create("h264", "r")
self.running = True
try:
while self.running:
# 接收分块数据
raw_h264 = self.video_socket.recv(0x10000)
if not raw_h264:
logger.info("[-] 视频流断开")
break
# 提取并解码帧
packets = codec.parse(raw_h264)
for packet in packets:
frames = codec.decode(packet)
for frame in frames:
rgb_frame = frame.to_ndarray(format="rgb24")
#frame.save("./1.jpg")
#logger.info("正在获取 视频流")
# 队列满了就扔掉旧帧,保证无延迟显示
if self.frame_queue.full():
try:
self.frame_queue.get_nowait()
except queue.Empty:
pass
self.frame_queue.put(rgb_frame)
except Exception as e:
logger.info(f"[-] 解码线程异常: {e}")
finally:
self.running = False
async def run_server(self):
# 启动后台解码线程
self.start_server()
decode_thread = Thread(target=self.decode_loop, daemon=True)
decode_thread.start()
# 【主协程】唯一负责渲染 UI 和处理事件循环的地方
while self.running:
try:
# 等待第一帧 (最多等待 100ms)
img = self.frame_queue.get(timeout=0.1)
# 排空队列:跳过所有积压的旧帧,只保留最新一帧
while True:
try:
img = self.frame_queue.get_nowait()
except queue.Empty:
break
# 只渲染最新帧 (ack_timeout=10ms,超时属正常行为)
render_start = time.time()
await self.rimg.render(img,)
render_ms = (time.time() - render_start) * 1000
logger.info(f"render: {render_ms:.1f}ms fps: {1000/render_ms:.1f}")
except queue.Empty:
pass
except TimeoutError:
pass # ack 超时 = 客户端还在渲染上一帧,正常跳过
except Exception as e:
logger.error(f"渲染异常: {e}")
self.running = False
To reproduce
1
Expected behavior
No response
Screenshots / Videos
Captures
[Upload media here]
Operating System
Windows
Operating system details
11
Flet version
0.86.5
Regression
No, it isn't
Suggestions
No response
Logs
INFO:services.scrcpy_core:render: 46.7ms fps: 21.4
INFO:services.scrcpy_core:render: 48.0ms fps: 20.9
INFO:services.scrcpy_core:render: 46.2ms fps: 21.6
INFO:services.scrcpy_core:render: 45.4ms fps: 22.0
INFO:services.scrcpy_core:render: 47.2ms fps: 21.2
INFO:services.scrcpy_core:render: 49.7ms fps: 20.1
INFO:services.scrcpy_core:render: 44.7ms fps: 22.4
INFO:services.scrcpy_core:render: 47.8ms fps: 20.9
INFO:services.scrcpy_core:render: 46.7ms fps: 21.4
INFO:services.scrcpy_core:render: 47.7ms fps: 21.0
INFO:services.scrcpy_core:render: 43.5ms fps: 23.0
INFO:services.scrcpy_core:render: 56.7ms fps: 17.7
INFO:services.scrcpy_core:render: 49.8ms fps: 20.1
INFO:services.scrcpy_core:render: 30.6ms fps: 32.7
INFO:services.scrcpy_core:render: 29.0ms fps: 34.5
INFO:services.scrcpy_core:render: 38.1ms fps: 26.3
INFO:services.scrcpy_core:render: 28.8ms fps: 34.7
INFO:services.scrcpy_core:render: 33.8ms fps: 29.6
INFO:services.scrcpy_core:render: 45.1ms fps: 22.2
INFO:services.scrcpy_core:render: 49.7ms fps: 20.1
INFO:services.scrcpy_core:render: 42.4ms fps: 23.6
INFO:services.scrcpy_core:render: 42.4ms fps: 23.6
INFO:services.scrcpy_core:render: 41.8ms fps: 23.9
INFO:services.scrcpy_core:render: 38.2ms fps: 26.2
INFO:services.scrcpy_core:render: 36.2ms fps: 27.6
INFO:services.scrcpy_core:render: 37.4ms fps: 26.7
INFO:services.scrcpy_core:render: 36.8ms fps: 27.2
INFO:services.scrcpy_core:render: 36.9ms fps: 27.1
INFO:services.scrcpy_core:render: 38.0ms fps: 26.3
INFO:services.scrcpy_core:render: 38.1ms fps: 26.3
INFO:services.scrcpy_core:render: 33.8ms fps: 29.6
INFO:services.scrcpy_core:render: 32.2ms fps: 31.0
INFO:services.scrcpy_core:render: 36.3ms fps: 27.5
INFO:services.scrcpy_core:render: 45.4ms fps: 22.0
INFO:services.scrcpy_core:render: 53.9ms fps: 18.6
INFO:services.scrcpy_core:render: 43.2ms fps: 23.2
INFO:services.scrcpy_core:render: 42.8ms fps: 23.4
INFO:services.scrcpy_core:render: 45.3ms fps: 22.1
INFO:services.scrcpy_core:render: 46.7ms fps: 21.4
INFO:services.scrcpy_core:render: 32.7ms fps: 30.6
INFO:services.scrcpy_core:render: 51.3ms fps: 19.5
Additional details
- Ngôn ngữ chính
- Python
- Star
- 17.1k
- Fork
- 708
- Merge trung bình
- 1 ngày 18 giờ
- Pull request đã merge (30 ngày)
- 45
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của flet-dev/flet
-
bug controls
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug: Interactive Viewer renders grey text when passed alignmentCó thể đã có người làm @ndonkoHenri đã nhận 5 ngày trước. Đang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
flet-dev/flet#6744 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
feature request
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug: Runtime packaged application fails with misleading module 'flet' has no attribute 'ElevatedButton' errorCó thể đã có người làm @ndonkoHenri đã nhận 2 ngày trước. Đang mởbug
flet-dev/flet#6894 · 2 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của flet-dev/flet
Issue tương tự
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
Maintainer thường phản hồi trong vòng 1 ngày
-
https://search.utilibre.orgĐang mởinstance instance add
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
searxng/searx-instances#941 · 1 bình luận ·
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 92/100
FluidNumerics/fluid-walk-blocker#89 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
Maintainer thường phản hồi trong vòng 1 ngày