scripts: 修 udp_probe.py 的重组与 fps 两处测量错误
探测脚本是诊断工具,测错比不测更糟——它给出的数字会被当成结论。
**重组状态必须按 (sender_id, frame_id) 存。** 原先只按 sender_id 存,于是丢片
之后下一帧的分片会接着凑上一帧的计数,凑够了交出去一张混着两帧的 JPEG,表现为
"JPEG 不完整"。修完:14 秒 132 帧,0 张坏帧。
换帧时被放弃的半成品单独计为"被新帧取代",不并入"JPEG 不完整"——它们本来就
不完整,混在一起会把两种情形都算成链路有问题。
**分窗 fps 的分子分母必须同量纲。** 原先用累计帧数除以窗口时长,于是报出 39、
61、79 这种随时间单调上升的假数字,看起来像板子越跑越快。改成窗口增量除以窗
口时长后稳定在 22,也就是传感器上限。
顺带修正两处与代码不符的注释:
partitions.csv / sdkconfig.defaults
两处都写死 app 分区在 0x40000,实际是 scripts/flash.sh 自动挑选并写回,
当前为 0x060000。补上 flash.sh 会重编译并复验的原因(重编译会让镜像位移,
所以偏移无法是常量)。
.env.example
补 CAM_VSYNC_INVERT 和 WIFI_AUTH。WIFI_AUTH 一直能用但没进文档:通常不用
设,扫描会报告 AP 的真实认证方式;只有扫不到时(隐藏 SSID)才需要指定,
指定后只试这一种,不成功直接报错而不是挨个试 10 秒——挨个试等于在猜一个
本该已知的值。
This commit is contained in:
+10
-1
@@ -5,7 +5,16 @@ WIFI_SSID=your-ssid
|
|||||||
WIFI_PASS=your-password
|
WIFI_PASS=your-password
|
||||||
|
|
||||||
# Optional
|
# Optional
|
||||||
|
|
||||||
# Capture resolution: qqvga | qvga | cif | hvga | vga | svga | xga | sxga | uxga
|
# Capture resolution: qqvga | qvga | cif | hvga | vga | svga | xga | sxga | uxga
|
||||||
#CAM_RES=vga
|
#CAM_RES=vga
|
||||||
# JPEG quality 0-63, lower is better quality
|
# JPEG quality 0-63, lower is better quality
|
||||||
#CAM_QUALITY=12
|
#CAM_QUALITY=12
|
||||||
|
# Set to 0 if your board inverts VSYNC in hardware.
|
||||||
|
#CAM_VSYNC_INVERT=1
|
||||||
|
|
||||||
|
# Pin the authentication mode instead of trusting the scan: none | wep | wpa |
|
||||||
|
# wpa2 | wpawpa2 | wpa3 | wpa2wpa3 | wapi. Only needed when the scan cannot see
|
||||||
|
# the AP (a hidden SSID, for instance) - otherwise the scan reports the real
|
||||||
|
# mode and this only makes it fail faster.
|
||||||
|
#WIFI_AUTH=wpa2
|
||||||
|
|||||||
+11
-8
@@ -1,14 +1,17 @@
|
|||||||
# Name, Type, SubType, Offset, Size, Flags
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
#
|
#
|
||||||
# The app partition starts at 0x40000 rather than the usual 0x10000 on purpose:
|
# The app partition offset here is chosen by scripts/flash.sh, not by hand. It
|
||||||
# the module this firmware was first brought up on has a defective byte in its
|
# sits well above the usual 0x10000 because the module this firmware was first
|
||||||
# SPI flash at 0xFADE7 (bits 5..7 cannot be programmed). The app image is
|
# brought up on has a defective byte in its SPI flash at 0xFADE7 (bits 5..7
|
||||||
# unchanged by where it is placed, so moving the partition moves which image byte
|
# cannot be programmed). The app image is unchanged by where it is placed, so
|
||||||
# lands on that address - and at 0x40000 the byte there is 0x1A, which the flash
|
# moving the partition moves which image byte lands on that address - and
|
||||||
# accepts.
|
# because a rebuild can shift the image again, re-deriving the offset by hand
|
||||||
|
# would break on the next build. flash.sh picks the lowest 64 KB-aligned offset
|
||||||
|
# whose byte at that address the flash can actually store, writes it back here,
|
||||||
|
# and rebuilds.
|
||||||
#
|
#
|
||||||
# If you move to healthy hardware, put `factory` back at 0x10000 and delete the
|
# If you move to healthy hardware, put `factory` back at 0x10000 and delete
|
||||||
# note in sdkconfig.defaults; nothing else depends on this.
|
# BAD_FLASH_ADDR from scripts/flash.sh; nothing else depends on this.
|
||||||
nvs, data, nvs, 0x9000, 0x6000,
|
nvs, data, nvs, 0x9000, 0x6000,
|
||||||
phy_init, data, phy, 0xf000, 0x1000,
|
phy_init, data, phy, 0xf000, 0x1000,
|
||||||
factory, app, factory, 0x060000, 0x120000,
|
factory, app, factory, 0x060000, 0x120000,
|
||||||
|
+48
-15
@@ -103,6 +103,9 @@ def main():
|
|||||||
sizes = []
|
sizes = []
|
||||||
start = time.time()
|
start = time.time()
|
||||||
last_print = start
|
last_print = start
|
||||||
|
prev_frames = 0
|
||||||
|
prev_pkts = 0
|
||||||
|
prev_drops = 0
|
||||||
|
|
||||||
while time.time() - start < duration:
|
while time.time() - start < duration:
|
||||||
try:
|
try:
|
||||||
@@ -135,15 +138,35 @@ def main():
|
|||||||
counters["载荷超过总长"] += 1
|
counters["载荷超过总长"] += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
state = pending.setdefault(
|
# 重组状态必须按 (sender_id, frame_id) 存,缺一不可。
|
||||||
sid,
|
#
|
||||||
{"buf": bytearray(h["total_len"]), "seen": 0, "expect": h["frag_count"],
|
# 之前这里只按 sender_id 存,于是某一帧丢了分片之后,下一帧的分片会接着往
|
||||||
"have": set(), "total": h["total_len"], "w": h["width"], "h": h["height"],
|
# 同一个计数器里凑,凑够了就交出去一张混着两帧的坏 JPEG——报成 34 张
|
||||||
"started": time.time()},
|
# "JPEG 不完整",看起来像重复分片或丢包的证据,其实是这个键选错了。
|
||||||
)
|
# protocol crate 的 FrameAssembler 一直按 frame_id 区分(换个 frame_id
|
||||||
|
# 就重置),这里原先跟它不一致。
|
||||||
|
key = (sid, h["frame_id"])
|
||||||
|
|
||||||
# 重复分片直接跳过且不计数:否则一个丢失的分片会被副本凑数,
|
state = pending.get(key)
|
||||||
# seen 达标而缓冲区留空洞,交出去一张尾部是 0 的坏图。
|
if state is None:
|
||||||
|
# 新的一帧到了:丢掉之前那帧的半成品。实时画面宁可少一帧,也不等它。
|
||||||
|
# 这些帧算"被新帧取代",不是"JPEG 不完整"——它们本来就不完整,
|
||||||
|
# 混在一起会把两种情形都算成链路有问题。
|
||||||
|
counters["被新帧取代"] += len(pending)
|
||||||
|
pending = {
|
||||||
|
key: {
|
||||||
|
"buf": bytearray(h["total_len"]),
|
||||||
|
"have": set(),
|
||||||
|
"expect": h["frag_count"],
|
||||||
|
"total": h["total_len"],
|
||||||
|
"w": h["width"],
|
||||||
|
"h": h["height"],
|
||||||
|
"started": time.time(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state = pending[key]
|
||||||
|
|
||||||
|
# 同一帧内的重复分片直接跳过且不计数:否则 seen 达标而缓冲区仍有空洞。
|
||||||
if h["frag_index"] in state["have"]:
|
if h["frag_index"] in state["have"]:
|
||||||
counters["重复分片"] += 1
|
counters["重复分片"] += 1
|
||||||
continue
|
continue
|
||||||
@@ -151,26 +174,36 @@ def main():
|
|||||||
|
|
||||||
off = h["frag_index"] * FRAG
|
off = h["frag_index"] * FRAG
|
||||||
state["buf"][off:off + len(payload)] = payload
|
state["buf"][off:off + len(payload)] = payload
|
||||||
state["seen"] += 1
|
|
||||||
|
|
||||||
tail = (h["total_len"] - 1) // FRAG
|
tail = (h["total_len"] - 1) // FRAG
|
||||||
if state["seen"] >= state["expect"] and tail in state["have"]:
|
if len(state["have"]) >= state["expect"] and tail in state["have"]:
|
||||||
jpeg = bytes(state["buf"][:state["total"]])
|
jpeg = bytes(state["buf"][:state["total"]])
|
||||||
if jpeg[:2] == b"\xff\xd8" and jpeg[-2:] == b"\xff\xd9":
|
if jpeg[:2] == b"\xff\xd8" and jpeg[-2:] == b"\xff\xd9":
|
||||||
counters["完整帧"] += 1
|
counters["完整帧"] += 1
|
||||||
sizes.append((state["total"], state["w"], state["h"]))
|
sizes.append((state["total"], state["w"], state["h"]))
|
||||||
else:
|
else:
|
||||||
counters["JPEG 不完整"] += 1
|
counters["JPEG 不完整"] += 1
|
||||||
del pending[sid]
|
del pending[key]
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if now - last_print >= 2.0:
|
if now - last_print >= 2.0:
|
||||||
|
# 分子分母必须都是本窗口的增量。早先用累计帧数除以窗口时长,于是报出
|
||||||
|
# 39、61、79 这种随时间单调上升的假 fps——看起来像板子越跑越快。
|
||||||
elapsed = now - last_print
|
elapsed = now - last_print
|
||||||
good = counters["完整帧"]
|
window_frames = counters["完整帧"] - prev_frames
|
||||||
|
window_pkts = counters["type:video"] - prev_pkts
|
||||||
|
window_drops = counters["JPEG 不完整"] - prev_drops
|
||||||
|
loss = 100.0 * window_drops / max(window_frames + window_drops, 1)
|
||||||
print(
|
print(
|
||||||
f" {good / elapsed:5.1f} fps "
|
f" {window_frames / elapsed:5.1f} fps "
|
||||||
f"视频包={counters['type:video']:5d} "
|
f"包/s={window_pkts / elapsed:5.0f} "
|
||||||
f"完整帧={good:4d} 丢弃={counters['JPEG 不完整']:3d}"
|
f"完整帧={counters['完整帧']:4d} "
|
||||||
|
f"丢弃={window_drops:3d} ({loss:.1f}%)"
|
||||||
|
)
|
||||||
|
prev_frames, prev_pkts, prev_drops = (
|
||||||
|
counters["完整帧"],
|
||||||
|
counters["type:video"],
|
||||||
|
counters["JPEG 不完整"],
|
||||||
)
|
)
|
||||||
last_print = now
|
last_print = now
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -35,8 +35,9 @@ CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
|
|||||||
# only 1 MB - smaller than the ~1.15 MB this firmware produces, so the image
|
# only 1 MB - smaller than the ~1.15 MB this firmware produces, so the image
|
||||||
# overflows the partition.
|
# overflows the partition.
|
||||||
#
|
#
|
||||||
# The custom table also moves the app to 0x40000, which is what keeps the image
|
# The custom table also carries an app offset that keeps the image off the
|
||||||
# off the module's defective flash byte; see partitions.csv for why.
|
# module's defective flash byte. That offset is written by scripts/flash.sh, not
|
||||||
|
# fixed here; see partitions.csv for why it cannot be a constant.
|
||||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="../../../../../../partitions.csv"
|
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="../../../../../../partitions.csv"
|
||||||
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
||||||
|
|||||||
Reference in New Issue
Block a user