feat: 链路控制、延迟校准与长测工具,补全文档
构建客户端 / 检查(类型 + 单元测试) (push) Canceled after 0s
构建客户端 / Linux x64(deb + AppImage) (push) Canceled after 0s
构建客户端 / Windows x64(NSIS 安装包) (push) Canceled after 0s
构建客户端 / macOS 通用(.app + .dmg) (push) Canceled after 0s

- 固件:新增 link_control / stream_control,覆盖运行中 Wi-Fi 断线恢复、取帧节拍、
  温控与质量/帧率减载;UDP 发送增加有界重试预算与帧期限,PSRAM JPEG 直发减少拷贝。
- 协议:重组器严格校验分片布局,统计未收齐、超时与完全缺失的帧,处理帧号回绕与设备重启。
- 桌面端:视频改 latest-value 通道并前置帧时间标记,拒绝积压和慢解码的过期画面;新增
  延迟标记页与 --latency-report 校准,完善录制、服务端与退出流程。
- 文档与脚本:新增硬件、开发、延迟、功耗、实时性、稳定性文档和改动记录,重写根 README;
  新增构建、性能、延迟与串口长测脚本,以及长测客户端脚本。
- 构建:固定 esp-1.97.0.0 工具链,分区表随构建复制,更新 sdkconfig 与忽略规则。
This commit is contained in:
2026-10-19 00:19:07 +08:00
parent 4794bed2cf
commit cc6b82d7ec
60 changed files with 4347 additions and 655 deletions
+24 -2
View File
@@ -733,13 +733,14 @@ camera_fb_t *cam_take(TickType_t timeout)
for (;;)
{
TickType_t elapsed = xTaskGetTickCount() - start; /* TickType_t is unsigned so rollover is safe */
if (elapsed >= timeout) {
if (timeout && elapsed >= timeout) {
ESP_LOGW(TAG, "Failed to get frame: timeout");
return NULL;
}
TickType_t remaining = timeout - elapsed;
TickType_t remaining = timeout ? timeout - elapsed : 0;
if (xQueueReceive(cam_obj->frame_buffer_queue, (void *)&dma_buffer, remaining) == pdFALSE) {
if (!timeout) return NULL;
continue;
}
@@ -750,6 +751,7 @@ camera_fb_t *cam_take(TickType_t timeout)
if (dma_reset_counter < MAX_GDMA_RESETS) {
ll_cam_dma_reset(cam_obj);
dma_reset_counter++;
if (!timeout) return NULL;
continue; /* retry with queue timeout */
}
if (dma_reset_counter == MAX_GDMA_RESETS) {
@@ -762,6 +764,7 @@ camera_fb_t *cam_take(TickType_t timeout)
CAM_WARN_THROTTLE(warn_null_cnt,
"Unexpected NULL frame on " CONFIG_IDF_TARGET);
#endif
if (!timeout) return NULL;
vTaskDelay(1); /* immediate yield once resets are done */
continue; /* go to top of loop */
}
@@ -802,6 +805,7 @@ skip_eoi_check:
CAM_WARN_THROTTLE(warn_eoi_miss_cnt,
"NO-EOI - JPEG end marker missing");
cam_give(dma_buffer);
if (!timeout) return NULL;
continue; /* wait for another frame */
} else if (cam_obj->psram_mode &&
cam_obj->in_bytes_per_pixel != cam_obj->fb_bytes_per_pixel) {
@@ -818,6 +822,24 @@ skip_eoi_check:
}
}
camera_fb_t *cam_take_latest(TickType_t timeout, uint32_t *skipped)
{
if (skipped) *skipped = 0;
camera_fb_t *latest = cam_take(timeout);
if (!latest) return NULL;
/* A bounded drain, not availability-check + blocking get: the producer can
* replace queued buffers on the other core. Retain the valid frame if a
* newer candidate is invalid or a GDMA-reset sentinel is encountered. */
for (size_t i = 1; i < cam_obj->frame_cnt; i++) {
camera_fb_t *next = cam_take(0);
if (!next) break;
cam_give(latest);
latest = next;
if (skipped) (*skipped)++;
}
return latest;
}
void cam_give(camera_fb_t *dma_buffer)
{
for (int x = 0; x < cam_obj->frame_cnt; x++) {