杰理 SDK 文档中心
首页
首页
  • 概述与快速开始

    • SDK 总览与芯片能力
    • 环境搭建与编译构建
    • 烧录与固件升级
    • 文档与版本资源
  • 应用与示例方案

    • demo 示例工程
    • WiFi 摄像头方案 (wifi_camera)
    • WiFi 音箱方案 (wifi_soundbox)
    • WiFi 婴儿监护方案 (wifi_bbm)
    • 公共应用模块库
    • 示例代码库 (example)
  • 系统架构与平台

    • 总体架构与工程分层
    • 系统启动与运行框架
    • 芯片驱动与板级适配
    • 设备管理与文件系统
    • 系统工具库与算法
  • 音频子系统

    • 音频框架与处理节点
    • 音频编解码与音效
    • 播放器与录音器
    • 语音交互与 AI 唤醒
    • LE Audio 与蓝牙音频
    • 音频调试与歌词
  • 视频与显示子系统

    • 摄像头驱动与 ISP
    • 视频编码与图像处理
    • 显示与 GPU 加速
    • 屏幕镜像 (screen_mirror)
  • 无线连接与网络

    • 蓝牙协议栈 (双模蓝牙)
    • WiFi 协议栈与配网
    • 网络协议栈
    • 云平台与 IoT 协议
  • UI 子系统

    • LVGL 集成与应用
    • UI 工程与工具链
  • 配置系统

    • 功能配置
    • 板级配置
    • 网络与蓝牙配置
    • 音频配置与提示音
  • 工具与测试

    • 产测与射频测试工具
    • 固件升级与更新机制
    • 调试与日志工具
  • 硬件参考设计

    • 原理图参考设计
    • 芯片数据手册

屏幕镜像 (screen_mirror)

屏幕镜像(screen_mirror)模块是 AC792 SDK 中负责通过 TCP/UDP 网络接收远端 JPEG 视频流并在本地 LCD 上实时显示的组件。它以内嵌数据源插件(scr0)配合 jpeg_dec / rep / imc / disp 解码显示流水线的方式,将手机、PC 等设备推送的屏幕画面镜像到开发板屏幕,支持帧率/回包两种流控策略与统计监测。

Purpose and Scope

本文档系统性地说明 screen_mirror 子系统的完整实现,包括:

  • screen_mirror_api:面向应用层暴露的初始化/反初始化/状态查询接口(net_scr_init / net_scr_uninit / get_net_scr_status);
  • screen_mirror_adapter:作为 pipeline 数据源插件 scr0 的收包、JPEG 分包重组、线程调度与统计逻辑;
  • 网络协议细节(frm_head 帧头、分片偏移、校验码)、TCP/UDP 双协议支持、ack 流控模式;
  • 配置结构体 __NET_SCR_CFG 的每个字段含义与默认行为。

以下主题不属于本页范围,请参考对应目录页:LCD 显示与合成(disp/imc 插件)、JPEG 解码器(jpeg_dec)、视频采集/编码(wifi_camera 方案)、通用 pipeline 框架(pipeline_core)。

概述

屏幕镜像本质上是一条“网络 JPEG 流 → 解码 → 缩放 → 格式转换 → 合成显示”的实时视频链路。设备端作为 TCP 服务端(或 UDP 接收端)监听固定端口 NET_SCR_PORT,接收对端发来的 JPEG 帧;每帧可能被拆分为多个分片(slice),由适配层依据帧头中的 seq/offset/frm_sz 重组为完整 JPEG 数据后送入解码显示流水线。

整个模块由 CONFIG_NET_SCR 编译宏控制,未定义该宏时相关代码不会编译进固件。模块设计上复用了 SDK 的 pipeline 插件框架:screen_mirror_api.c 只负责搭链与参数下发,真正收包逻辑在 screen_mirror_adapter.c 的 scr0 插件中,二者通过 pipeline 参数(PIPELINE_SCR_CLI_ADR 等)和全局句柄 g_scr_used 解耦。

关键设计决策:

  1. 协议无关的显示链路:网络差异被封装在 scr0 数据源插件内,解码/显示部分与传输层完全无关;
  2. 两种流控模式:ack=0 以本地解码帧率为准(推流端按 fps 发送),ack=1 以回包为准(每收一帧回发 ACK,推流端等 ACK 再发下一帧),适配不同网络质量;
  3. 单通道设计:SCR_CHANNEL_MAX = 1,同一时刻仅支持一路屏幕镜像,保证 LCD 显示资源独占。

架构

flowchart TD
    subgraph sg_Remote["对端设备 (推流端)"]
        Sender["手机/PC 屏幕采集 + JPEG 编码"]
        Encoder["TCP/UDP 发送"]
    end

    subgraph sg_App["应用层 (App)"]
        App["业务 App 代码"]
        API["screen_mirror_api.c<br/>net_scr_init / net_scr_uninit"]
    end

    subgraph sg_Pipeline["Pipeline 解码显示链路"]
        SRC["scr0 数据源插件<br/>screen_mirror_adapter.c"]
        JD["jpeg_dec 插件"]
        REP["rep 缩放插件"]
        IMC["imc 格式转换插件"]
        DISP["disp 显示插件"]
    end

    subgraph sg_Net["网络层 (sock_api)"]
        SOCK["sock_reg / bind / listen<br/>SOCK_STREAM / SOCK_DGRAM"]
    end

    subgraph sg_Hw["硬件"]
        LCD["LCD 屏幕"]
        NET["WiFi / 以太网"]
    end

    Sender --> Encoder --> NET --> SOCK
    SOCK --> SRC
    SRC -->|"JPEG 帧 (重组后)"| JD --> REP --> IMC --> DISP --> LCD
    App --> API
    API -->|"pipeline_filter_add / link / prepare / start"| SRC
    API -->|"PIPELINE_SCR_CLI_ADR / SOCK_TYPE / ACK_CALLBACK"| SOCK
    API -->|"PIPELINE_SET_FORMAT"| JD
    SRC -->|"ack_cb 回包"| SOCK

上图展示了屏幕镜像的两条关键链路:

  • 数据链路(右侧):对端通过 WiFi/以太网将 JPEG 流发送到设备绑定的 NET_SCR_PORT 端口,scr0 插件收包并重组出完整 JPEG 帧,随后依次经过 jpeg_dec(解码为 YUV)、rep(缩放)、imc(格式转换/裁剪)、disp(合成显示到 LCD);
  • 控制链路(左侧):应用通过 net_scr_init 传入 __NET_SCR_CFG,API 层创建 pipeline、按名称添加 scr0 源插件并依次 link 各滤波器,再把网络参数(客户端地址、socket 类型、ack 回调)经 pipeline 参数下发到 scr0 插件。

该分层刻意将网络细节与显示细节隔离:scr0 只关心“从 socket 拿到完整 JPEG”,disp 只关心“把 YUV 帧显示出来”,任何一层替换(例如把 JPEG 换成 H.264 源)都不影响另一层。

主要实现分析

API 层:net_scr_init 的搭链流程

net_scr_init(screen_mirror_api.c)是屏幕镜像的入口,其执行顺序严格遵循 pipeline 生命周期:初始化 → 添加滤波器 → 设置参数 → 链接 → prepare → start。

int net_scr_init(struct __NET_SCR_CFG *cfg)
{
    pipe_filter_t *source_filter, *jpeg_dec_filter, *imc_filter, *rep_filter, *disp_filter;
    struct video_format f = {0};

    if (__this->state) {
        log_error("%s multiple init\n", __func__);
        return 0;
    }
    memcpy(&__this->cli_addr, &cfg->cli_addr, sizeof(struct sockaddr_in));

    log_info("scr size: %d x %d", cfg->src_w, cfg->src_h);
    log_info("scr fps: %d, prot: %s, ack: %d", cfg->fps, (cfg->prot == 0) ? "tcp" : "udp", cfg->ack);

    __this->pipe_core = pipeline_init(NULL, NULL);
    ASSERT(__this->pipe_core);

    char *source_name = "scr0";
    __this->pipe_core->channel = plugin_source_to_channel(source_name);

    source_filter = pipeline_filter_add(__this->pipe_core, source_name);
    jpeg_dec_filter = pipeline_filter_add(__this->pipe_core, plugin_factory_find("jpeg_dec"));
    rep_filter = pipeline_filter_add(__this->pipe_core, plugin_factory_find("rep"));
    imc_filter = pipeline_filter_add(__this->pipe_core, find_use_for_display_plugin("imc"));
    disp_filter = pipeline_filter_add(__this->pipe_core, plugin_factory_find("disp"));
    ...
}

Source: screen_mirror_api.c

设计要点:

  • 重复初始化防护:__this->state 为 1 时直接返回 0,避免同一 pipe 被重复创建导致资源泄漏;
  • 源插件按名字查找:pipeline_filter_add(pipe, "scr0") 走插件名注册表,scr0 即 screen_mirror_adapter.c 注册的数据源插件;plugin_source_to_channel(source_name) 把源名解析为 channel 号;
  • imc 插件选择:find_use_for_display_plugin("imc") 而非直接 plugin_factory_find,说明 imc 有多个变体(如 DMA2D 加速版),按当前显示配置动态挑选可用实现;
  • 错误处理为硬断言:ASSERT(__this->pipe_core) 在内存不足时直接停机,适合嵌入式实时场景,避免悬空指针继续执行。

参数下发与格式配置

初始化后半段通过 pipeline_param_set 下发视频格式与网络参数:

    //数据源数据格式
    f.src_width = cfg->src_w;
    f.src_height = cfg->src_h;
    //imc不做帧率控制,以下发帧率为准
    f.fps = cfg->fps;

    //显示配置
    f.win.left 	 = 0;
    f.win.top  	 = 0;
    f.win.width = f.src_width;
    f.win.height = f.src_height;
    f.win.combine = 1; //合成显示

    pipeline_param_set(__this->pipe_core, NULL, PIPELINE_SET_FORMAT, &f);
    pipeline_param_set(__this->pipe_core, NULL, PIPELINE_SCR_CLI_ADR, &cfg->cli_addr);
    int sock_type = (cfg->prot == 0) ? SOCK_STREAM : SOCK_DGRAM;
    pipeline_param_set(__this->pipe_core, NULL, PIPELINE_SCR_SOCK_TYPE, &sock_type);
    pipeline_param_set(__this->pipe_core, NULL, PIPELINE_SCR_ACK_CALLBACK, &cfg->ack_cb);
    int line_cnt = 16;
    pipeline_param_set(__this->pipe_core, NULL, PIPELINE_SET_BUFFER_LINE, (int)&line_cnt);

Source: screen_mirror_api.c

含义与设计意图:

  • f.win.combine = 1 声明该视频窗口参与合成显示(overlay),可与其他摄像头/UI 窗口叠加,而不是独占屏幕;
  • PIPELINE_SCR_* 系列参数是屏幕镜像模块自定义的私有参数,由 scr0 插件在 scr_prepare/scr_handle_param 中消费——这正是 API 层与适配层解耦的接口契约;
  • PIPELINE_SET_BUFFER_LINE = 16 控制解码输出的缓冲行数,直接影响内存占用与延迟(行数越少延迟越低,但解码吞吐受限);
  • fps 被直接写入 video_format,注释明确“imc 不做帧率控制,以下发帧率为准”,即显示帧率完全由推流端节奏决定。

适配层:scr0 插件与收包状态机

screen_mirror_adapter.c 注册了名为 scr0 的数据源插件。其核心私有数据结构 struct scr_handle 覆盖了单路镜像的全部运行状态:

struct scr_handle {
    u8 channel;
    u8 ref;
    u8 state;
    spinlock_t lock;

    int socket_type;
    void (*ack_cb)(int);
    void *sock_hdl;
    void *cli_sock_hdl;

    u8 sock_fps;
    u8 disp_fps;
    u32 seq;

    struct sockaddr_in cli_addr;
    int src_width;
    int src_height;
    u8 fps;
    u32 old_frame_seq;
    u32 jpg_size;

    int pid;	// 收包线程
};

Source: screen_mirror_adapter.c

该句柄的生命周期由插件状态机驱动:PLUGIN_UNINIT → PLUGIN_INITED → PLUGIN_READY → (运行) → PLUGIN_UNINIT。scr_init 中通过 check_channel_legal 从插件名末尾解析 channel 号(如 scr0 → 0),并校验不超过 SCR_CHANNEL_MAX(1),不合法直接 ASSERT——从插件命名规范上杜绝了多路镜像同时启动的可能。

全局句柄表 g_scr_used[SCR_CHANNEL_MAX] 保证同一 channel 只分配一次内存,重复 init 时复用既有句柄。

网络初始化:TCP 服务端 / UDP 接收端

socket_type 由 API 层根据 cfg->prot 换算(0→SOCK_STREAM,1→SOCK_DGRAM),插件在 scr_prepare 阶段调用 net_scr_sock_init 完成绑定:

    hdl->sock_hdl = sock_reg(AF_INET, hdl->socket_type, 0, NULL, NULL);
    if (hdl->sock_hdl == NULL) {
        log_error("socket reg failed.");
        return -1;
    }

    if (sock_set_reuseaddr(hdl->sock_hdl)) {
        log_error("socket reuseaddr failed.");
        sock_unreg(hdl->sock_hdl);
        hdl->sock_hdl = NULL;
        return -1;
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port  = htons(NET_SCR_PORT);

    ret = sock_bind(hdl->sock_hdl, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));
    ...
    if (hdl->socket_type == SOCK_STREAM) {
        ret = sock_listen(hdl->sock_hdl, 0xFF);
        ...
    }

Source: screen_mirror_adapter.c

要点:

  • 绑定 INADDR_ANY + NET_SCR_PORT,对推流端地址不设限(cli_addr 仅在反初始化时用于校验,见下文);
  • sock_set_reuseaddr 允许快速重启时复用 TIME_WAIT 端口,避免异常退出后短时间内无法重新绑定;
  • TCP 模式下 sock_listen(hdl, 0xFF) 设置全连接队列深度 255;UDP 模式跳过 listen;
  • 任何一步失败都会 sock_unreg 回滚并返回 -1,scr_prepare 收到非零后 free(hdl) 并终止,保证失败路径不留半初始化资源。

核心流程

端到端数据流时序

sequenceDiagram
    participant App as 应用层
    participant API as screen_mirror_api
    participant SRC as scr0 插件
    participant SOCK as sock_api
    participant Sender as 推流端 (TCP/UDP)
    participant PL as jpeg_dec/rep/imc/disp

    App->>API: net_scr_init(&cfg)
    API->>API: pipeline_init + filter_add(scr0/jpeg_dec/rep/imc/disp)
    API->>API: pipeline_param_set(PIPELINE_SET_FORMAT / SCR_*)
    API->>API: pipeline_filter_link × 4
    API->>API: pipeline_prepare / pipeline_start
    API->>SRC: scr_prepare → net_scr_sock_init(bind NET_SCR_PORT)
    SRC->>SRC: 创建收包线程 scr_recv
    API-->>App: 返回 0

    Sender->>SOCK: 连接/发送 JPEG 分片
    SOCK->>SRC: 收包线程 recv
    SRC->>SRC: get_jpg_packet 解析 frm_head<br/>按 seq/offset 重组完整 JPEG
    SRC->>PL: 送入解码显示链路
    PL-->>SRC: 帧消费完成
    alt ack=1 (以回包为准)
        SRC->>SOCK: 调用 ack_cb 回包 ACK
        SOCK-->>Sender: ACK
        Sender->>SOCK: 发送下一帧
    else ack=0 (以帧率为准)
        Sender->>SOCK: 按 cfg->fps 持续推流
    end

    App->>API: net_scr_uninit(&cfg)
    API->>API: 校验 cli_addr (跳过 sin_family/port 后 6 字节)
    API->>SRC: pipeline_stop / reset / uninit
    API-->>App: 返回 0

时序图中值得注意的细节:

  1. 地址校验技巧:net_scr_uninit 用 memcmp((char *)&__this->cli_addr + 2, (char *)&cfg->cli_addr + 2, 6) 比较,跳过前 2 字节(sin_family 的 2 字节 + 端口高字节起始),实际比较的是端口(2) + 地址(4),防止不同会话误关别人的镜像通道;
  2. 收包线程:scr_recv(SCR_THREAD_TASK_NAME)由插件在启动时创建,pid 字段保存线程 id,是数据面唯一的并发点;
  3. 回包模式:仅 ack=1 时收完一帧立即触发 ack_cb,由应用层负责把 ACK 写回 socket 或交给推流协议处理——适配层只做“通知”,不做网络写回,保持职责单一。

JPEG 分片重组协议

推流端可能将一帧 JPEG 拆成多个 UDP/TCP 分片发送,每个分片携带 struct frm_head 帧头。get_jpg_packet 负责把分片还原为整帧:

    do {
        struct frm_head  *head_info = (struct frm_head *)(buf + position);
        frame_type = head_info->type & 0x7F;
        cur_frame_seq = head_info->seq;
        frame_offset = head_info->offset;
        slice_data_len = head_info->payload_size;
        frame_size = head_info->frm_sz;
        ...

Source: screen_mirror_adapter.c

帧头字段语义:

字段含义
type帧类型(低 7 位有效,高位可能携带结束/丢弃标志位)
seq帧序号,用于乱序识别与丢帧检测
offset该分片在整帧内的字节偏移,用于拼装
payload_size本分片有效载荷长度
frm_sz整帧 JPEG 总大小

重组策略要点:

  • 完整性校验:重组前先扫描整包中的 CHECK_CODE(0x88) × CHECK_CODE_NUM(32) 校验区,确认数据来自约定的推流协议(bytecmp 逐字节比对);
  • 乱序容忍:UDP 场景下分片可能乱序到达,重组逻辑以 offset 为索引写入 jpg 缓冲区,并以 seq 判断新旧帧,old_frame_seq 记录上一帧序号用于丢帧统计;
  • 缓冲上限:JPG_BUF_MAX_SIZE = 250 * 1024(约 244KB),超限分片会被丢弃,防止内存被恶意大帧撑爆;
  • UDP 单包上限:UDP_MAX_RECV = 1472 字节(以太网 MTU 1500 减去 IP/UDP 头),接收缓冲区按单包上限静态分配,避免堆上频繁 malloc。

运行统计

模块内置 struct scr_state 统计运行期指标:

struct scr_state {
    u8 fps_min;
    u8 fps_max;
    u32 fps_sum;

    float jpg_sz_min;  //KB
    float jpg_sz_max;
    u32 jpg_sz_sum;

    u32 cnt;
};

Source: screen_mirror_adapter.c

统计对象包括实际接收帧率(min/max/均值)与每帧 JPEG 大小(min/max/累计,单位 KB),cnt 为累计帧数。这些数据可用于调试网络质量、评估压缩率,是 Wi-Fi 屏镜像场景定位“卡顿/模糊”问题的第一手依据。

使用示例

公共 API 定义

屏幕镜像对外暴露的最小接口集合,定义在头文件中:

struct __JPG_INFO {
    u32 src_w;
    u32 src_h;
    u32 buf_len;
    u8  buf[];
};

struct __NET_SCR_CFG {
    struct sockaddr_in cli_addr;
    int fps;
    int src_w;
    int src_h;
    int prot;   //prot:协议类型 0->tcp 1->udp
    int ack;    //ack:规则类型 0->以帧率为准 1->以回包为准
    void *ack_cb;   //回包回调函数
};

u8 get_net_scr_status(void);
int net_scr_init(struct __NET_SCR_CFG *cfg);
int net_scr_uninit(struct __NET_SCR_CFG *cfg);

Source: screen_mirror_api.h

__JPG_INFO 是重组后的完整 JPEG 帧载体(src_w/src_h 为源分辨率,buf 为柔性数组成员);__NET_SCR_CFG 是唯一的配置入口,应用只需填好结构体即可启动镜像。

应用层调用方式(TCP 模式)

以 TCP + 帧率流控为例,应用代码先填写配置再调用初始化:

struct __NET_SCR_CFG cfg = {0};
cfg.cli_addr = ...;      // 对端地址(TCP 模式下实际由 accept 获得)
cfg.fps      = 30;       // 目标帧率
cfg.src_w    = 480;      // 源宽
cfg.src_h    = 854;      // 源高
cfg.prot     = 0;        // 0 -> tcp
cfg.ack      = 0;        // 0 -> 以帧率为准
cfg.ack_cb   = NULL;

if (net_scr_init(&cfg) == 0) {
    // 镜像运行中,可用 get_net_scr_status() 查询状态
}
...
net_scr_uninit(&cfg);     // 退出前反初始化

Source: screen_mirror_api.h

网络参数换算参考

视频链路中 mirror/rotate 字段的用法可在 wifi_camera 应用中看到对照示例:

req.display.mirror  = 0; //1镜像

Source: video_photo.c

/* req.display.mirror = VIDEO_HOR_MIRROR | VIDEO_VER_MIRROR; */
/* req.display.rotate = 90; //90/270 */

Source: video_rec.c

镜像与旋转是在视频显示请求(video_req/video_display_req)中配置的,screen_mirror 链路的 disp 插件同样遵循该显示语义,支持水平/垂直镜像组合与 90°/270° 旋转。

配置选项

编译开关

选项类型默认说明
CONFIG_NET_SCR宏关闭屏幕镜像模块总开关;未定义时 screen_mirror_api.c 与 screen_mirror_adapter.c 的模块代码整体不参与编译

__NET_SCR_CFG 运行期配置

字段类型默认/取值说明
cli_addrstruct sockaddr_in无推流端地址;net_scr_uninit 用它做会话校验(比较端口+IP 共 6 字节)
fpsint无目标帧率;写入 video_format.fps,imc 不控帧率,实际帧率以推流端为准
src_wint无源图像宽,决定显示窗口宽与解码缓冲尺寸
src_hint无源图像高,决定显示窗口高
protint0协议类型:0 → TCP(SOCK_STREAM),1 → UDP(SOCK_DGRAM)
ackint0流控规则:0 → 以帧率为准,1 → 以回包为准
ack_cbvoid *NULL回包回调函数指针;ack=1 时每收完一帧触发

适配层内部常量

常量值说明
NET_SCR_PORT由 rt_stream_pkg.h 定义监听端口,TCP/UDP 共用
SCR_CHANNEL_MAX1最大镜像通道数(单路)
SCR_THREAD_TASK_NAME"scr_recv"收包线程名
JPG_BUF_MAX_SIZE250 × 1024单帧 JPEG 重组缓冲上限(字节)
CHECK_CODE0x88协议校验码字节
CHECK_CODE_NUM32校验码连续字节数
UDP_MAX_RECV1472UDP 单包最大接收长度(MTU 预算内)
PIPELINE_SET_BUFFER_LINE16解码输出缓冲行数

API 参考

int net_scr_init(struct __NET_SCR_CFG *cfg)

初始化屏幕镜像:创建 pipeline、添加并链接 scr0/jpeg_dec/rep/imc/disp 滤波器、下发格式与网络参数、prepare 并 start。

参数:

  • cfg (struct __NET_SCR_CFG *):镜像配置,含地址、fps、分辨率、协议与流控模式。

返回: 0 成功;失败时可能为 -1(内部错误)或在 ASSERT 处直接停机(如 pipeline_init 返回 NULL)。

注意事项: 重复调用时打印 "multiple init" 并直接返回 0(幂等,不重建)。

int net_scr_uninit(struct __NET_SCR_CFG *cfg)

反初始化:校验会话地址匹配后 pipeline_stop → pipeline_reset → pipeline_uninit。

参数:

  • cfg (struct __NET_SCR_CFG *):需与 init 时地址一致。

返回: 0 成功;-1 表示未在运行或 cli_addr 不匹配(打印 "cli addr not match.")。

u8 get_net_scr_status(void)

返回: 1 表示镜像通道运行中,0 表示未启动/已停止。常用于业务轮询或退出条件判断。

失败模式、边界情况与并发

重复初始化与并发保护

  • net_scr_init 通过 __this->state 防止重复初始化;适配层通过 hdl->state != PLUGIN_UNINIT 跳过重复 scr_init,并通过全局句柄表 g_scr_used[channel] 保证单通道单实例。
  • 收包线程 scr_recv 与主控线程通过 spinlock_t lock 保护句柄内共享字段(如 seq、sock_fps),get_jpg_packet 的重组缓冲区(total_payload_len/finish)为线程局部静态变量,天然避免跨线程竞争,但代价是同一时刻只能有一个收包线程在重组——与 SCR_CHANNEL_MAX = 1 的约束一致。
  • 反初始化路径:net_scr_uninit 先清 state 再 pipeline_stop,保证收包线程在 pipeline 停止前有机会退出,避免 use-after-free。

推流端异常场景

场景行为
推流端中途断开(TCP)收包返回错误/0,get_jpg_packet 打印 recv len err,重组状态可能残留;上层应调用 net_scr_uninit 复位后重新 init
分片包小于 sizeof(struct frm_head)直接返回 -1 丢弃,防越界解析
帧数据超过 JPG_BUF_MAX_SIZE超出部分截断/丢弃,防止大帧撑爆静态缓冲
UDP 乱序/丢包以 seq + offset 重组,丢帧由 old_frame_seq 检测并计入统计
非约定推流端(校验码不符)bytecmp 比对 0x88 × 32 失败,整包被拒绝

已知限制

  • 单通道设计(SCR_CHANNEL_MAX=1)不支持多路镜像同时显示;
  • cli_addr 仅用于 uninit 校验,TCP 模式的实际对端由 accept 决定,若多客户端连接,仅首路生效;
  • 掉电/异常复位时若未调用 net_scr_uninit,靠 sock_set_reuseaddr 缓解端口复用问题。

性能与运维考虑

  • 延迟预算:PIPELINE_SET_BUFFER_LINE = 16 限制解码缓冲行数,配合即时显示策略压低端到端延迟——这是屏镜像体验(触控跟手性)的关键参数,调大可提升解码容错但增加延迟;
  • 内存占用:单帧 JPEG 缓冲 250KB(静态区)+ UDP 收包缓冲 1472B(静态) + 解码显示链路各滤波器缓冲,镜像分辨率与 JPG_BUF_MAX_SIZE 需按可用 RAM 评估;
  • UDP 模式 MTU 约束:单包 1472B 保证不触发 IP 分片,减少重组开销,但推流端需自行分片(frm_head.offset 机制正是为此设计);
  • 可观测性:struct scr_state 的 fps 与 JPEG 大小统计(min/max/均值)可通过调试日志输出,用于评估网络带宽与压缩质量;fps_sum/jpg_sz_sum 为 32 位累计值,长时间运行时需留意溢出(约 1.7 亿帧/字节级)后再取均值;
  • 日志开关:LOG_TAG_CONST 分别定义为 SCREEN_MIRROR_API 与 SCREEN_MIRROR_ADAPTER,且 LOG_ERROR_ENABLE/LOG_INFO_ENABLE/LOG_DUMP_ENABLE 均已打开,产线固件可裁剪以省 flash/串口带宽。

扩展点

  1. 替换/新增数据源协议:scr0 插件严格遵循 pipeline 源插件接口(scr_init/scr_prepare/scr_connect + 参数回调),可通过注册新插件名(如 scr1)并修改 check_channel_legal 的 SCR_CHANNEL_MAX 扩展多路镜像;
  2. 自定义回包协议:ack_cb 是应用层钩子,ACK 帧格式完全由推流协议决定,适配层不关心内容,可对接私有握手/鉴权协议;
  3. 显示链路替换:find_use_for_display_plugin("imc") 允许按硬件能力选择 imc 变体(如 DMA2D GPU 加速版),解码/显示插件可通过 plugin_factory_find 替换为硬解实现;
  4. 统计上报:在 scr_state 更新处挂接日志或事件回调,即可实现帧率/码率的实时监控上报。

相关链接

  • screen_mirror_api.h — 公共接口与配置结构体定义
  • screen_mirror_api.c — pipeline 搭建与参数下发实现
  • screen_mirror_adapter.c — scr0 数据源插件、收包重组与统计
  • rt_stream_pkg.h — frm_head 帧头与 NET_SCR_PORT 等流协议定义
  • video_dec_server.h — 视频解码服务与 mirror/rotate 显示参数
  • 相关目录页:LCD 显示合成(disp/imc)、JPEG 解码(jpeg_dec)、Pipeline 框架(pipeline_core)、WiFi 摄像头方案(wifi_camera)
Prev
显示与 GPU 加速