WiFi 摄像头方案 (wifi_camera)
杰理 AC792N SDK 中基于 WL83 板卡的 WiFi 摄像头(行车记录仪/IPC)应用方案,涵盖任务调度、配置持久化、音频处理、LVGL UI、LTE 与 WiFi 联网等完整端到端实现。
Purpose and Scope
本文档介绍 sdk/apps/wifi_camera 应用的整体架构与关键实现,包括:
- 应用入口
app_main.c的中断表、任务表与静态任务栈分配(系统启动与调度骨架) app_database.c的配置项数据库(录像、系统、拍照、WiFi 四类配置的键值定义与读写机制)- 音频(
mic_effect、scene_switch、tone_table、vol_sync)、LVGL v8 UI(lvgl_v8_ui_app)、LTE(lte_app_task、lte_network_test)等子系统在方案中的职责 - WiFi 摄像头特有的配置(预览分辨率、WiFi 开关/运行模式、开机音等)
本页聚焦于应用层的组织方式与可配置行为。底层 WiFi 协议栈、LVGL 框架本身、以及音频编解码器(G.711 等)的算法细节属于平台/中间件范畴,不在本页展开;如需了解请参见对应 SDK 组件文档。
Overview
wifi_camera 是 AC792N(双核 MCU,CPU_CORE_NUM 可配)上的一个完整产品级应用示例,其形态兼具行车记录仪与WiFi 摄像头两种角色:既支持本地录像/拍照(分辨率、循环录像、重力感应、停车守卫、夜视增强等车载功能),又可通过 WiFi 提供预览/回传通道(ctp_server),并通过 lte_app_task 支持 LTE 联网能力。UI 采用 LVGL v8,多语言与本地模拟数据(sim_database)支撑快速原型开发。
应用层通过三个核心机制组织:
- 静态任务系统:在
task_info_table中声明app_core、sys_event、audio_server等任务的优先级、栈大小与消息队列,配合irq_info_table完成双核中断归属。 - 配置数据库:
app_database.c用一张紧凑的db_table表(键名 + 字节数 + 默认值)定义全部用户可调配置,通过app_set_config()统一写入并db_update()持久化到 flash。 - 消息驱动:
app_msg.c提供应用消息分发,配合事件系统(key/bt/device/net)驱动 UI 与业务状态切换。
flowchart TD
subgraph sg_Board["board/wl83"]
BoardDev["board_develop.c 板级初始化"]
SdkCfg["sdk_config.c 芯片/外设配置"]
end
subgraph sg_App["app 核心层"]
AppMain["app_main.c 中断表+任务表"]
AppMsg["app_msg.c 消息分发"]
AppDb["app_database.c 配置数据库"]
KeyVoice["key_voice.c 按键语音"]
Backlight["backlight.c 背光控制"]
end
subgraph sg_Media["音视频/媒体"]
G711["g711.c G.711 编解码"]
MicEffect["audio/mic_effect.c"]
SceneSwitch["audio/scene_switch.c"]
ToneTable["audio/tone_table.c"]
VolSync["audio/vol_sync.c"]
end
subgraph sg_Net["联网能力"]
WifiConn["wifi/wifi_connect.h 连接管理"]
CtpServer["ctp_server.h 摄像头传输"]
LteTask["lte/lte_app_task.c"]
LteTest["lte/lte_network_test.c"]
end
subgraph sg_UI["LVGL v8 UI"]
UIStyle["style_JL/custom 多语言/动画/模拟数据"]
end
AppMain --> AppMsg
AppMain --> AppDb
AppMain --> KeyVoice
AppMain --> Backlight
AppMain --> WifiConn
AppMain --> CtpServer
AppDb --> AppMsg
AppMsg --> UIStyle
AppMain --> sg_Media
AppMain --> LteTask
LteTask --> LteTest
AppMain --> BoardDev
BoardDev --> SdkCfg
架构说明:app_main.c 是应用唯一入口,负责把系统任务(app_core、sys_event、音频任务族)和 WiFi/LTE 协议栈线程拉起来;app_database.c 是全局配置的事实来源(source of truth),UI 与业务逻辑通过消息(app_msg)读写它;音视频、联网、UI 三者作为对等的子系统挂在核心层之下,由任务/消息解耦,便于按产品形态裁剪(例如去掉 LTE 或关闭 WiFi)。
应用入口与任务系统 (app_main.c)
app_main.c 是 wifi_camera 方案的启动源头,定义了两种系统级静态表:中断列表 与 任务列表。
中断表:双核中断归属
/*中断列表 */
const struct irq_info irq_info_table[] = {
//中断号 //优先级0-7 //注册的cpu(0或1)
{ IRQ_BT_TIMEBASE_IDX, 5, 0 },
{ IRQ_BLE_RX_IDX, 5, 0 },
{ IRQ_BLE_EVENT_IDX, 5, 0 },
{ IRQ_BT_CLKN_IDX, 5, 0 },
{ IRQ_BREDR_IDX, 5, 0 },
#if CPU_CORE_NUM == 1
{ IRQ_SOFT5_IDX, 6, 0 }, //此中断强制注册到cpu0
{ IRQ_SOFT4_IDX, 6, 1 }, //此中断强制注册到cpu1
{ -2, -2, -2 }, //如果加入了该行, 那么只有该行之前的中断注册到对应核, 其他所有中断强制注册到CPU0
#endif
{ -1, -1, -1 },
};
Source: app_main.c
设计意图:AC792N 是双核 SoC,蓝牙(BR/EDR + BLE)与 WiFi 的中断必须绑定到固定核心以保证协议栈时序。表尾的哨兵行 {-1,-1,-1} 终止遍历;{-2,-2,-2} 是可选屏障——加入后,其之前的中断按行内指定注册,其余中断一律强制到 CPU0,用于规避双核中断竞争。
静态任务栈:避免运行时 malloc
#define APP_CORE_STK_SIZE 2048
#define APP_CORE_Q_SIZE 1024
static u8 app_core_tcb_stk_q[sizeof(StaticTask_t) + APP_CORE_STK_SIZE * 4 + sizeof(struct task_queue) + APP_CORE_Q_SIZE] ALIGNE(4);
/*创建使用 thread_fork 接口的 静态任务堆栈*/
#define WIFI_TASKLET_STK_SIZE 1400
static u8 wifi_tasklet_tcb_stk_q[sizeof(struct thread_parm) + WIFI_TASKLET_STK_SIZE * 4] ALIGNE(4);
#define WIFI_CMDQ_STK_SIZE 300
static u8 wifi_cmdq_tcb_stk_q[sizeof(struct thread_parm) + WIFI_CMDQ_STK_SIZE * 4] ALIGNE(4);
#define WIFI_MLME_STK_SIZE 900
static u8 wifi_mlme_tcb_stk_q[sizeof(struct thread_parm) + WIFI_MLME_STK_SIZE * 4] ALIGNE(4);
#define WIFI_RX_STK_SIZE 256
static u8 wifi_rx_tcb_stk_q[sizeof(struct thread_parm) + WIFI_RX_STK_SIZE * 4] ALIGNE(4);
Source: app_main.c
这些 static u8 ...[] ALIGNE(4) 数组在编译期静态分配,包含 TCB(StaticTask_t 或 struct thread_parm)+ 栈 + 消息队列,4 字节对齐保证栈访问效率。wifi_* 四个线程栈专门预留给 WiFi 协议栈的 tasklet/命令队列/MLME/RX 路径——这些路径运行在中断上下文附近,静态栈能保证任何时刻都有可用栈空间,避免嵌入式系统常见的动态分配失败。
任务表:优先级与队列配置
/*任务列表 */
const struct task_info task_info_table[] = {
{"thread_fork_kill", 25, 256, 0 },
{"app_core", 15, APP_CORE_STK_SIZE, APP_CORE_Q_SIZE, app_core_tcb_stk_q },
{"sys_event", 29, SYS_EVENT_STK_SIZE, 0, sys_event_tcb_stk_q },
{"systimer", 14, SYSTIMER_STK_SIZE, 0, systimer_tcb_stk_q },
{"sys_timer", 9, SYS_TIMER_STK_SIZE, SYS_TIMER_Q_SIZE, sys_timer_tcb_stk_q },
{"dlog", 1, 256, 128 },
{"audio_server", 16, 512, 64 },
{"audio_mix", 28, 512, 0 },
{"audio_encoder", 12, 384, 64 },
{"mp3_encoder", 13, 768, 0 },
{"vir_dev_task", 14, 256, 0 },
{"vad_encoder", 14, 768, 0 },
{"aec_encoder", 13, 1024, 0 },
{"dns_encoder", 13, 512, 0 },
{"msbc_encoder", 13, 256, 0 },
{"sbc_encoder", 13, 512, 0 },
{"adpcm_encoder", 13, 512, 0 },
{"dynamic_huffman0", 15, 300, 32 },
{"dynamic_huffman1", 15, 300, 32 },
{"echo_deal", 11, 1024, 32 },
{"uac_sync", 20, 512, 0 },
{"uac_play0", 26, 512, 32 },
{"uac_play1", 26, 512, 32 },
{"uac_record0", 26, 512, 0 },
Source: app_main.c
每个任务条目为 {名称, 优先级, 栈大小, 消息队列大小, 可选静态栈}。数值越小优先级越高,因此 dlog(1)与 sys_timer(9)优先,保证日志与系统定时不丢;app_core(15)是应用主线程,带 1024 字的消息队列承载 UI/业务事件;audio_server/audio_encoder(12/16)保证音频链路实时性。任务表是理解整个方案实时性预算的入口:WiFi 摄像头相比纯音频方案额外引入了 WiFi 协议栈线程,因此在 #ifdef CONFIG_WIFI_ENABLE 下音频/编码任务的取舍(如 mp3/sbc/adpcm 编码器)需要按产品形态裁剪。
配置数据库 (app_database.c)
app_database.c 用单一静态表定义全部用户配置项,是方案的可配置行为总目录:
static const struct db_table app_config_table[] = {
/* 录像模式配置项 */
{"mic", 1, 1}, // 录音开关
{"mot", 1, 0}, // 移动侦测开关
{"par", 1, 1}, // 停车守卫开关
{"wdr", 1, 0}, // 夜视增强开关
{"num", 1, 0}, // 车牌开关
{"dat", 1, 1}, // 时间标签开关
{"two", 1, 1}, // 双路开关
{"gra", 2, GRA_SEN_MD}, // 重力感应灵敏度
{"gap", 4, 0}, // 间隔录影设置
{"res", 3, VIDEO_RES_720P}, // 录像分辨率
{"cyc", 4, 3}, // 循环录像时间,单位分钟
{"exp", 8, 0}, // 曝光, 范围-3到+3
{"cna", 32, CN_PA}, // 车牌号码--京
{"cnb", 32, CN_PB}, // 车牌号码 --A 12
{"cnc", 32, CN_PC}, // 车牌号码 --345
{"proc", 32, 0}, // 车牌号码 --345
{"hdr", 8, 0}, // HDR
/* 系统模式配置项 */
{"kvo", 1, 1}, // 按键音开关
{"lag", 4, 1}, // 语言设置
{"fre", 8, 50}, // 灯光频率
{"aff", 8, 0}, // 自动关机时间, 单位分钟
{"pro", 8, 0}, // 屏幕保护时间, 单位秒
{"tvm", 5, TVM_PAL}, // 电视制式
{"lan", 8, 0}, // 轨道偏移
{"hlw", 1, 0}, // 前照灯提醒开关
{"datey", 32, 2024}, // 年
{"datem", 5, 5}, // 月
{"dated", 5, 20}, //日
{"dateh", 7, 13}, // 时
{"datemi", 7, 14}, // 分
{"dates", 7, 10}, // 秒
/* 拍照模式配置项 */
{"sok", 1, 0}, // 防手抖开关
{"pdat", 1, 0}, // 图片日期标签开关
{"cyt", 1, 0}, // 连拍开关
{"qua", 3, PHOTO_QUA_HI}, // 图片质量
{"acu", 8, PHOTO_ACU_HI}, // 图片锐度
{"phm", 8, 0}, // 延时拍照, 单位秒
{"pres", 6, PHOTO_RES_1M}, // 图片分辨率
{"wbl", 4, PHOTO_WBL_AUTO}, // 白平衡
{"col", 4, PHOTO_COLOR_NORMAL}, // 颜色模式
{"sca", 8, 0}, // 快速预览时间,单位s
{"pexp", 8, 0}, // 曝光设置,范围-3到+3
{"iso", 8, 0}, // iso
{"stk", 8, 0}, // 大头贴索引
{"zoom", 8, 10}, // 数字变焦,为了方便传参,实际值除以10
{"dac", 32, 0x55aa}, // dac_trim
#ifdef CONFIG_WIFI_ENABLE
{"rtf", 3, VIDEO_RES_720P}, //预览前视分辨率
{"rtb", 3, VIDEO_RES_VGA}, //预览后视分辨率
{"wfo", 1, 1}, //wifi开关
{"wfmode", 1, 0}, // wifi运行模式
{"bvo", 1, 1}, // 开机音开关
#endif
};
Source: app_database.c
该表的设计要点:
- 紧凑三列结构
{键名, 值字节数, 默认值}:键名 3~5 个 ASCII 字符,写进bitstore/syscfg时开销极小;字节数即该键在 flash 中占用的位宽,跨键共享一个存储字以节约 flash(例如datey占 32 位、datem占 5 位)。 - 默认值即出厂值:首次上电或恢复出厂时由
db_update机制回填,保证无配置 flash 时也能以 720P 录像、PAL 制式、中文语言等合理默认运行。 - 条件编译嵌入 WiFi 专属键:
CONFIG_WIFI_ENABLE开启时才存在rtf/rtb/wfo/wfmode/bvo,使同一份代码可编译出"带 WiFi 的摄像头"与"纯行车记录仪"两种产品。 - 多字节值的编码技巧:车牌号码用字符移位拼成整型(
CN_PC = ('3'<<24)|('4'<<16)|('5'<<8)|(' '<<0)),zoom实际值除以 10 存储,避免引入浮点。
配置写入流程与 API
用户配置的写入统一走 app_set_config():
int app_set_config(struct intent *it, const struct app_cfg *cfg, int size)
{
int i;
printf("app_set_config: %s, %d\n", it->data, it->exdata);
for (i = 0; i < size; i++) {
if (!strcmp(it->data, cfg[i].table)) {
if (cfg[i].set) {
int err = cfg[i].set(it->exdata);
if (err) {
return err;
}
}
db_update(cfg[i].table, it->exdata);
}
}
Source: app_database.c
app_set_config(it: struct intent*, cfg: const struct app_cfg*, size: int): int
- 参数:
it->data是配置键名(如"res"),it->exdata是待写入的新值;cfg为运行时配置表(含可选的set回调);size为表长度。 - 返回:0 表示成功;若
set回调返回非 0(例如分辨率切换失败),立即返回该错误码且不落盘。 - 行为:先调用
cfg[i].set(it->exdata)让业务模块即时生效(如切换传感器分辨率),成功后再db_update()写入 flash。这种"先生效、后持久化"的顺序保证掉电时最多丢失一次设置,而不会出现 flash 已更新但硬件状态不一致的情况。 - 调用方:UI(LVGL)通过
app_msg携带 intent 触发,按键/语音(key_voice.c)也可直接调用,是 UI、按键、语音三类输入的统一配置出口。
sequenceDiagram
participant UI as LVGL UI / 按键 / 语音
participant MSG as app_msg 消息队列
participant DB as app_database.c
participant HW as 业务模块(传感器/编解码)
participant FLASH as syscfg/bitstore 存储
UI->>MSG: intent{data="res", exdata=VIDEO_RES_1080P}
MSG->>DB: app_set_config(it, cfg, size)
DB->>DB: 遍历 app_config_table 匹配键名
DB->>HW: cfg[i].set(exdata) 即时生效
HW-->>DB: err==0 ?
alt err != 0
DB-->>UI: 返回错误码(不落盘)
else err == 0
DB->>FLASH: db_update("res", exdata) 持久化
DB-->>UI: 返回 0
end
核心数据流:从按键到配置生效
flowchart TD
A["用户操作(按键/触摸/语音)"] --> B["key_event / 触摸事件"]
B --> C["app_msg.c 消息封装"]
C --> D{"键名匹配 app_config_table?"}
D -->|"是"| E["app_set_config()"]
D -->|"否"| F["普通业务消息 → 各子系统"]
E --> G{"cfg["i"].set 存在?"}
G -->|"是"| H["调用 set 回调即时生效"]
G -->|"否"| I["仅更新存储"]
H --> J["db_update() 写 flash"]
I --> J
J --> K["syscfg/bitstore 持久化"]
关键路径说明:
- 用户在 UI 上修改"循环录像时间",LVGL 回调把
{"cyc", 3}封装成 intent 投递到app_core任务的消息队列(APP_CORE_Q_SIZE 1024)。 app_set_config线性扫描app_config_table(50 个键以内,O(n) 查找在低功耗 MCU 上可接受,且表在 flash 中只读、无哈希开销)。- 匹配后先执行
set回调让编码器/传感器立即按新参数工作,再db_update持久化;重启后app_database初始化时从存储回读,恢复用户设置。 - 若键名不匹配配置表,则按普通业务消息分发到对应子系统(背光、语音、UI 动画等),保持"配置"与"命令"两条通道分离,避免 UI 逻辑与存储逻辑耦合。
WiFi 与联网相关配置
在 CONFIG_WIFI_ENABLE 编译开关下,方案额外提供 5 个 WiFi 摄像头专属配置键:
| 键名 | 字节 | 默认值 | 含义 |
|---|---|---|---|
rtf | 3 | VIDEO_RES_720P | 预览前视分辨率 |
rtb | 3 | VIDEO_RES_VGA | 预览后视分辨率 |
wfo | 1 | 1 | WiFi 开关 |
wfmode | 1 | 0 | WiFi 运行模式(STA/AP 等) |
bvo | 1 | 1 | 开机音开关 |
这些键与 app_main.c 中预分配的 wifi_tasklet/wifi_cmdq/wifi_mlme/wifi_rx 四个线程栈、wifi/wifi_connect.h 连接管理、ctp_server.h 摄像头传输服务共同构成 WiFi 摄像头链路:rtf/rtb 决定无线预览推流的分辨率(前视/后视双路),wfmode 决定设备以 Station 模式入网还是以 AP 模式被直连。LTE 能力(lte_app_task.c、lte_network_test.c)则提供蜂窝回传通道,适合无 WiFi 环境的远程预览场景。
子模块职责一览
| 模块 | 文件 | 职责 |
|---|---|---|
| 音频增强 | audio/mic_effect.c | 麦克风音效(录音/通话场景) |
| 场景切换 | audio/scene_switch.c | 音频场景(音乐/录音/提示音)切换 |
| 音调表 | audio/tone_table.c | 按键音/提示音的音调数据 |
| 音量同步 | audio/vol_sync.c | 多路音频输出音量同步 |
| G.711 编解码 | g711.c | 语音通话/对讲的 A-law/μ-law 编解码 |
| 按键语音 | key_voice.c | 按键触发的语音播报 |
| 背光控制 | backlight.c | 屏幕背光亮度/省电策略 |
| LTE 任务 | lte/lte_app_task.c | LTE 模组应用任务(拨号/状态机) |
| 网络测试 | lte/lte_network_test.c | LTE 网络连通性测试 |
| UI 自定义 | lvgl_v8_ui_app/style_JL/custom/* | 多语言、模拟数据库、动画(action_animation/linedrift/list) |
| 板级初始化 | board/wl83/board_develop.c | WL83 板卡外设初始化 |
| SDK 配置 | board/wl83/sdk_config.c | 芯片级功能裁剪配置 |
| 工程文件 | board/wl83/AC792N_WIFI_CAMERA.cbp | Code::Blocks 工程(编译单元清单) |
其中 lvgl_v8_ui_app/style_JL/custom/sim_database.c 提供 UI 开发的模拟数据源——在产品硬件未就绪时,UI 团队可先用模拟数据跑通界面,再切换到真实 app_database.c 数据,这是该方案支持 UI/驱动并行开发的关键设计。
失败模式与边界情况
- 配置写入失败不落盘:
app_set_config中set回调返回非 0 时(如传感器不支持目标分辨率),函数直接返回错误码,db_update不会执行,避免"存储值与硬件实际状态不一致"。 - 双核中断竞争:
CPU_CORE_NUM == 1时通过{-2,-2,-2}屏障把未显式分配的中断全部压到 CPU0,防止 WiFi/BT 与摄像头 ISP 中断跨核竞争导致时序抖动;若使用双核,必须保持irq_info_table中各中断的核归属与协议栈要求一致。 - 静态栈溢出风险:
task_info_table中栈大小是编译期常量,wifi_tasklet(1400 字)等深度调用路径若改动协议栈或增加回调层数,必须复查栈预算;静态分配虽消除了 malloc 失败,但溢出表现为静默踩内存,排查困难。 - flash 写入次数:
db_update每次配置变更都写存储;摄像头方案按键/菜单操作频繁,若产品需高寿命,应在db_update层增加去抖或延迟合并写入(当前实现为直接写,属示例级简化)。 - 分辨率切换时序:
rtf/rtb切换预览分辨率时,set回调需先停流再切 sensor 模式,否则可能花屏;该时序由业务模块保证,配置层只负责串行调用。
性能与运维注意事项
- 实时任务优先级:
audio_server(16) /audio_encoder(12) 高于app_core(15) 与sys_event(29),保证音频/录像链路优先于 UI 事件;WiFi 任务(tasklet 等)通过thread_fork独立栈运行,不与应用任务争抢app_core队列。 - 消息队列背压:
app_core队列仅 1024 字,UI 动画/按键连发时若生产快于消费,消息会丢弃;LVGL 动画应限流,避免在高帧率录像时抢 CPU。 - dac_trim 校准:配置表末尾的
dac(默认0x55aa)是 DAC 校准值,产线校准会回写该键;升级固件时不得用默认值覆盖已校准设备。
扩展点
- 新增配置项:在
app_config_table追加{"键名", 字节数, 默认值},并在app_cfg运行时表中注册set回调即可,无需改动消息机制。 - 新增业务子系统:在
task_info_table声明新任务(名称/优先级/栈/队列),在app_msg注册消息类型,UI 通过 intent 驱动。 - 产品形态裁剪:通过
CONFIG_WIFI_ENABLE、CPU_CORE_NUM、TCFG_USB_*、THIRD_PARTY_PROTOCOLS_SEL等宏在保持同一套代码的前提下编译出"WiFi 摄像头 / 行车记录仪 / 对讲机"等不同产品。 - LTE 回传:
lte/lte_app_task.c与lte_network_test.c为独立任务,可整体裁剪;接入新模组只需替换lte_app_task内部的 AT 指令序列。