

ATS308X U 盘 MP4 音视频播放
本功能播放复制到板内 U 盘的 MP4 文件。这里的"U 盘"指开发板通过 USB MSC 暴露给电脑的内部 NAND FAT 分区,固件挂载路径为 `/NAND:`,不是插入外部 USB Host 的 U 盘。电脑复制完成并安全弹出磁盘后,板端才能打开同一文件;不要让电脑和固件同时修改 FAT 文件系统。
本次使用真正的 MP4 容器、H.264 Baseline 软件视频解码和 AAC-LC 软件音频解码。显示仍经本机 LVGL / 显示驱动,声音经 PCM 音频播放通路输出。`.mp4` 扩展名不代表里面一定是 H.264/AAC,也不保证分辨率和码率适合 MCU。
- 支持范围与推荐规格
| 项目 | 当前板端目标规格 / PC 工具输出 |
| --- | --- |
| 容器 | 普通非碎片 MP4,`ftyp` + `moov` + `mdat`;推荐 `+faststart` |
| 视频轨 | 单条 H.264,sample entry 为 `avc1` |
| H.264 profile / level | Baseline 或 Constrained Baseline;PC准备/检查要求level ≤ 1.3 |
| 帧类型 | I / P 帧,无 B 帧;PC准备固定参考帧 1 |
| 像素格式 | 8 位 `yuv420p`,逐行扫描 |
| 分辨率 | 宽高均为偶数且 ≤ 240;默认输出 240 × 240,保持比例并加黑边 |
| 帧率 | ≤ 15 fps;默认固定 10 fps |
| 码率 | PC 工具默认视频 192 kbit/s,峰值约束 384 kbit/s |
| 音频轨 | 可选单条 AAC-LC,`mp4a`,单声道 16 kHz / 24 kHz |
| 默认音频 | AAC-LC,单声道 16 kHz,32 kbit/s |
| 文件 / 时长 | ≤ 512 MiB、≤ 30 分钟;单个压缩包 ≤ 256 KiB;路径使用 `/NAND:/文件名.mp4` |
| 不支持的输入 | HEVC/H.265、H.264 High/Main/B 帧、HE-AAC、加密媒体、碎片 MP4、字幕/多视频轨 |
推荐先用随附 3 秒样片,再测试自己的文件。软件 H.264 解码的负载取决于画面内容、码率、参考帧和显示负载;规格检查通过仍需在开发板上确认帧率及声音连续性。不要将手机原始 1080p/4K MP4 直接当作板端素材。
推荐输入以PC工具转码并检查通过的文件为准。固件在打开文件和解码过程中检查容器、编码、宽高、帧率、音频参数和包尺寸等;PC检查比固件的初始声明检查更严格,额外约束level和轨道数量等。固件没有把level或参考帧1写成逐项拒绝规则,不能把"没有报错"理解为更复杂文件也已验证可用。
- PC 准备:Python、FFmpeg 与工具
工具位置:`application/bt_watch/tools/mp4/mp4_tool.py`。兼容工程使用的 Python 3.8,无额外 Python 依赖。需要安装包含 `libx264` 与 AAC 编码器的 FFmpeg,以及配套 `ffprobe`,可从 FFmpeg 下载页(https://ffmpeg.org/download.html) 选择 Windows 构建。可把 `bin` 加入 PATH,或通过 `--ffmpeg` / `--ffprobe` 指定完整路径。工具不会向仓库安装可执行程序。
在 `` 根目录的 PowerShell 执行:
```powershell
ffmpeg -version
ffprobe -version
python application/bt_watch/tools/mp4/mp4_tool.py --help
```
2.1 将已有视频转为板端 MP4
```powershell
python application/bt_watch/tools/mp4/mp4_tool.py prepare `
"D:\videos\input.mp4" "D:\videos\board_240.mp4" `
--size 240 --fps 10 `
--report "D:\videos\board_240_report.json"
```
没有把 FFmpeg 加入 PATH 时,在上述命令末尾追加:
```powershell
--ffmpeg "C:\tools\ffmpeg\bin\ffmpeg.exe" `
--ffprobe "C:\tools\ffmpeg\bin\ffprobe.exe"
```
输入也可以是 MKV、MOV 等 FFmpeg 能解码的本地视频,输出始终使用 `-f mp4`,不会把 MOV 或 AVI 简单改后缀。没有音轨的输入会输出静音视频。
| 参数 | 功能 |
| --- | --- |
| `prepare 输入 输出` | 读取已有视频并转码,输出必须为 `.mp4` |
| `--size 240` | 正方形画布边长,16~240 的偶数;保留原始宽高比并补黑边 |
| `--fps 10` | 固定帧率,1~15;较低帧率有利于降低 CPU 负载 |
| `--no-audio` | 丢弃音轨,用于只测视频和定位音频问题 |
| `--overwrite` | 明确允许覆盖输出;默认拒绝覆盖已有文件 |
| `--ffmpeg` / `--ffprobe` | 指定两个可执行文件路径 |
| `--report` | 保存 UTF-8 JSON 规格报告,含编码、尺寸、帧率、box、兼容性与错误原因 |
在PyCharm中也可直接运行:Script path选择`application/bt_watch/tools/mp4/mp4_tool.py`,Working directory设为``,Parameters填写`prepare "D:\videos\input.mp4" "D:\videos\board_240.mp4" --size 240 --fps 10`;解释器使用Python 3.8或更高版本。FFmpeg未加入PATH时,同样在Parameters追加两个可执行文件的完整路径参数。
工具底层的关键 FFmpeg 参数如下,便于理解复现:
```text
-map 0:v:0 -map 0:a:0?
-vf scale=240:240:force_original_aspect_ratio=decrease,pad=240:240:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=10,format=yuv420p
-c:v libx264 -preset veryfast -profile:v baseline -level:v 1.3
-pix_fmt yuv420p -bf 0 -refs 1 -g 20
-x264-params cabac=0:bframes=0:ref=1:scenecut=0
-b:v 192k -maxrate 384k -bufsize 384k
-c:a aac -profile:a aac_low -ar 16000 -ac 1 -b:a 32k
-map_metadata -1 -map_chapters -1 -movflags +faststart -f mp4
```
`-map 0:a:0?` 的 `?` 表示允许输入没有音轨;`-bf 0` 禁用 B 帧;`-refs 1` 限制参考图像内存;`cabac=0` 使用 Baseline 的 CAVLC 熵编码;`-g 20` 在默认 10 fps 下约每 2 秒产生一个关键帧。`+faststart` 把索引 `moov` 放到媒体 `mdat` 前面,便于打开文件时先取得轨道参数,不会改变 H.264/AAC 算法。
2.2 生成 / 检查 3 秒测试样片
仓库提供 `application/bt_watch/tools/mp4/samples/demo_240.mp4`:240 × 240、10 fps、H.264 Constrained Baseline、AAC-LC 16 kHz 单声道;画面为测试图,声音为 440 Hz 正弦波。它不包含外部素材。
```powershell
python application/bt_watch/tools/mp4/mp4_tool.py demo "D:\videos\demo_240.mp4"
python application/bt_watch/tools/mp4/mp4_tool.py inspect `
"D:\videos\demo_240.mp4" --report "D:\videos\demo_240_report.json"
```
`inspect` 正常时返回码 0、JSON 中 `compatible=true`;不兼容或文件错误返回码 2。它检查容器顶层边界、真正 MP4 品牌、轨道编码、profile、尺寸、帧率、B 帧声明、音频参数,以及文件大小、时长和全部压缩包的最大字节数。它不是完整码流解码器,不能证明损坏的每个包都能解码,也不承诺开发板实时性能。
2.3 复制和播放
-
普通调试串口执行 `app udisk on`,通过 USB 连接电脑,看到开发板内部 U 盘。
-
把样片复制到 U 盘根目录,建议先用 ASCII 文件名 `demo_240.mp4`。工程已附带 `application/bt_watch/tools/mp4/samples/demo_240.mp4`,首次测试可直接复制,无需先安装 FFmpeg。
-
在电脑安全弹出磁盘,再通过普通串口执行 `app udisk off`,归还 FAT 并恢复板端挂载。
-
普通调试串口执行以下命令,或使用 MP4 界面文件列表 / 控制按钮。
```text
app udisk off
app mp4 list
app mp4 play /NAND:/demo_240.mp4
app mp4 status
app mp4 pause
app mp4 resume
app mp4 stop
app mp4 show
app mp4 hide
```
播放期间不要重新连接电脑读写同一 NAND 分区。`show` 打开播放器界面;`hide`、界面失去焦点或退出界面都会停止播放并释放资源。暂停应使用`pause`或触摸播放/暂停按钮,不能用隐藏界面代替。播放器状态和错误以串口日志为准。
触摸入口为"更多 → MP4",也可执行 `app mp4 show`。界面提供文件列表、播放/暂停、停止、文件和返回。文件按钮先停止当前视频,再显示列表;停止后可以再次选择文件或播放上次选择的文件。串口控制和触摸控制操作的是同一个播放器。`status` 的停止、完成或错误终态在工作线程实际归还音频与存储资源后发布。
2.4 状态和串口日志
```text
mp4 META streams=2 duration_us=3000000
mp4 STREAM index=0 type=0 codec=27 size=240x240 rate=10/1 sample_rate=0 channels=0 profile=578
mp4 STREAM index=1 type=1 codec=86018 size=0x0 rate=0/0 sample_rate=16000 channels=1 profile=1
mp4 OPEN H264(CPU) 240x240 AAC=1 duration=3000 ms path=/NAND:/demo_240.mp4
mp4 PLAY time=2000/3000 decoded=24 dropped=0 queue=4 clock=audio-decoder
mp4 PAUSE time=2000
mp4 END error=0 time=3000 decoded=30 dropped=0 detail=Finished
```
这是字段示例,不是开发板实测结果。`META`输出流数量及汇总后的微秒时长,`STREAM`逐条输出轨道的编码、尺寸、帧率、采样率、声道及profile;`type`、`codec`和`profile`是当前FFmpeg头文件中的枚举数值,视频的`sample_rate=0/channels=0`及音频的`size=0x0/rate=0/0`属于不适用字段。`OPEN`表示文件及解码器已打开;`PLAY`是周期性进度;`decoded`是已解码视频帧数,`dropped`是因显示迟到而跳过显示的帧数,`queue`为待显示队列长度。`clock=audio-decoder`表示使用音频服务的PCM解码进度,`clock=wall`表示单调时钟。正常结束的`error=0`。主动停止打印`STOP`,失败打印`ERROR`并带负错误码及`detail`。
打开失败会先输出具体阶段,例如:
```text
mp4 OPEN_FAIL stage=video-fps error=-134
mp4 ERROR error=-134 time=0 decoded=0 dropped=0 detail=Open video-fps failed (-134)
```
可通过`app mp4 status`读取保留下来的`detail`,避免仅凭`-134`就判定全部是视频格式问题。常见阶段有`storage`、`file-stat`、`file-read`、`MP4-boxes`、`demux-header`、`stream-info`、`duration`、`H264-spec`、`video-fps`、`AAC-spec`、`AAC-profile`和`audio-output`;它们分别指向存储占用、文件、容器、流信息分析、规格或音频通路。异步`play`返回0只表示请求已入队,最终以`OPEN`或`OPEN_FAIL/ERROR`及状态查询为准。
出现`audio clock stalled/unavailable; fallback=wall`表示音频进度超过750ms没有推进,播放器切换时钟继续工作,需检查音频服务、CPU负载和队列欠载。这个回退避免进度永久卡死,不能掩盖声音异常。
- MP4 数据格式:容器如何装下音视频
MP4 基于 ISO Base Media File Format(ISO/IEC 14496-12),通常把每段结构叫 **box**,历史资料也称 atom。整数按大端序保存。普通 box 前 4 字节是包含自身头部的总长度,接下来 4 字节是 ASCII 类型,例如 `moov`。长度为 1 时,后续 8 字节给出 64 位长度;长度为 0 表示延伸到所在范围末尾。解析器必须检查长度溢出、父子边界和文件截断。
```text
MP4 文件
├─ ftyp 文件品牌、兼容品牌
├─ moov 时间和轨道索引;faststart 时在前
│ ├─ mvhd 整个电影的时间基、时长
│ ├─ trak 视频轨
│ │ ├─ tkhd 轨道 ID、显示尺寸等
│ │ └─ mdia
│ │ ├─ mdhd 该轨时间基 timescale、时长
│ │ ├─ hdlr vide / soun 标记媒体类型
│ │ └─ minf / stbl
│ │ ├─ stsd avc1 / avcC:编码及初始化信息
│ │ ├─ stts sample 解码时间增量
│ │ ├─ ctts 可选,显示时间相对解码时间的偏移
│ │ ├─ stsc sample 与 chunk 的对应关系
│ │ ├─ stsz 每个 sample 的字节长度
│ │ ├─ stco / co64 chunk 在文件中的绝对偏移
│ │ └─ stss 随机访问 / 关键帧 sample 编号
│ └─ trak 音频轨,相同索引框架;stsd 为 mp4a / esds
├─ free 可选填充
└─ mdat 实际 H.264 / AAC 压缩样本,通常交错存储
```
3.1 `ftyp`、`moov` 和 `mdat`
`ftyp` 声明 `isom` / `mp42` 等品牌。FFmpeg 对 MOV 和 MP4 使用同一类解复用器,因此 ffprobe 的 `format_name=mov,mp4,m4a,3gp,3g2,mj2` 不能单独证明文件是 MP4;还应检查 `ftyp`、major_brand 和真实 box。把 `.mov` 改为 `.mp4` 不会改变容器。
`moov` 提供"这是什么轨道、各个压缩样本在哪、什么时候解码/显示"的目录。`mdat` 仅保存压缩数据,不会在每个视频帧前附上人类可读的时间戳。若只搜索 `mdat` 而忽略 sample table,就无法可靠拆分音视频和同步。
`+faststart` 只移动 `moov` 及修正 chunk 偏移。碎片 MP4 使用 `moof/traf/trun` 等另一组索引,在本功能范围外。
3.2 sample、chunk 和 `stbl` 索引
一个 **sample** 是解复用器交给解码器的压缩单元,通常一个视频 sample 对应一个编码图像,一个 AAC sample 对应一个压缩音频帧。一个 **chunk** 则是文件中连续保存的一组同轨 samples,用于高效交错音视频。
查找第 n 个 sample 时:
-
用 `stsc` 判断它处于哪个 chunk、chunk 内第几个 sample,以及采用哪个 `stsd` 描述。
-
用 `stco` 或 `co64` 得到 chunk 的文件起始偏移。
-
用 `stsz` 累加该 chunk 内更早 samples 的大小,得到本 sample 的准确文件偏移和字节数。
-
用 `stts` 累计此前 sample 的时间增量得到 DTS;如有 `ctts`,再得到 PTS。
-
用 `stss` 判断随机访问点;没有 `stss` 通常表示所有 samples 都是同步样本。
`stts` / `ctts` 常使用"连续多少个 samples 具有相同增量"的压缩表,不一定每帧一行。表条目数、sample count 和偏移均来自文件,开发时应限制分配规模并进行一致性检查,不能按未经验证的值直接申请大内存。
3.3 `avc1` / `avcC`:H.264 并不是 JPEG 帧序列
`stsd` 中的视频 sample entry `avc1` 包含尺寸等描述;其子 box `avcC` 保存 AVCDecoderConfigurationRecord,包括 profile、level、NAL 长度字段宽度,以及 SPS / PPS 初始化参数。SPS 描述图像尺寸、参考帧等编码参数,PPS 描述图像编码选项。解码器打开时需要这些参数。
MP4 中 AVC sample 通常是 **长度前缀 NAL**,例如 `4 字节 NAL 长度NAL 数据...`;原始 H.264 Annex B 流通常使用 `00 00 00 01` 起始码。二者都是H.264数据,但封装不同。FFmpeg 的 MP4 demuxer与H.264 decoder通过 extradata / `avcC` 配合处理,不能直接把 sample 当成JPEG调用 `jpg_decode()`。
H.264 I帧主要使用图像内预测;P帧利用之前的参考图像进行运动补偿,残差经整数变换、量化和熵解码重建。解码器需要保存参考帧,所以分辨率、参考帧数量和profile共同影响内存。Baseline禁用B帧和CABAC,降低复杂性;它仍然是有帧间依赖的压缩视频。
3.4 `mp4a` / `esds`:AAC 初始化与PCM输出
音频 sample entry 是 `mp4a`(不是 `m4a`;`.m4a` 是常用音频文件扩展名)。`esds` 描述 MPEG-4 Elementary Stream,包含 DecoderSpecificInfo / AudioSpecificConfig,用于告知 AAC object type、采样率索引和声道配置。AAC-LC object type 为 2;HE-AAC 包含扩展工具,与本次受限方案不同。
MP4 AAC sample 一般不带每帧ADTS文件头,初始化信息来自 `esds`。AAC解码器进行熵解码、逆量化、频带恢复和IMDCT等处理,输出PCM采样。典型AAC-LC帧每声道1024个采样,在16kHz时约64ms,在24kHz时约42.667ms。PCM帧按采样数推进音频时间,不能按串口日志打印速度来播放。
- 从文件到屏幕和扬声器的原理
```text
/NAND:/demo_240.mp4
↓ 文件I/O / MP4解复用(moov索引、mdat包、PTS/DTS)
├─ H.264压缩包 + avcC → H.264软件解码 → YUV420图像
│ ↓ 色彩转换
│ RGB565帧缓冲
│ ↓ UI线程 / LVGL
│ 显示屏
└─ AAC压缩包 + esds → AAC-LC软件解码 → PCM采样
↓ 音频缓冲 / 音频播放服务
扬声器
```
**解复用不等于解码**:解复用处理MP4 box和sample table,输出仍是压缩的H.264/AAC;视频解码重建图像,音频解码重建PCM;渲染和音频输出负责最终设备显示/发声。
屏幕是RGB565,H.264解码通常输出平面YUV420。转换时应尊重每个plane的stride / linesize,不能假定每行字节数恰好等于宽度。RGB565每像素2字节,240×240一张约112.5KiB;YUV420约84.375KiB一张,解码器还需要参考帧、压缩包和内部结构。不能把这两个数字当成播放器全部RAM占用。
UI对象应由UI线程更新,解码线程仅投递帧状态和受保护的缓冲。显示缓冲的生命周期必须覆盖实际绘制过程,不能在回调返回后立即释放正在显示的内存。音频应通过有限队列/环形缓冲连续输出,不能一次把整部文件解码到RAM。
4.1 DTS、PTS和音画同步
`DTS` 是解码时间,`PTS` 是显示/播放时间;单位由轨道time base决定。例如 timescale=10240、sample delta=1024 时,每帧为100ms,即10fps。换算公式是 `时间秒 = timestamp / timescale`。音频和视频可以使用不同timescale,比较前必须换算。
有B帧时解码顺序和显示顺序可能不同,需要`ctts`和重排队列。本次通过Baseline、`-bf 0`限制,使视频通常DTS=PTS,降低重排内存和同步难度。容器仍可能含编辑列表`edts/elst`,例如AAC编码预延迟补偿;成熟demuxer会处理这些时间偏移,不能简单把第一包时间强制当成0。
播放时以单调时钟或音频实际播放时钟作为基准,将媒体PTS映射到当前播放位置。视频提前时等待,迟到时可按策略丢弃显示帧;不能随意丢弃仍会被后续P帧引用的解码输入。暂停应冻结媒体时间,恢复时重新建立时钟锚点;否则暂停几秒后所有画面都会被当作过期帧。停止需要等待工作线程退出,再释放解码器、文件、音频和UI缓冲。
4.2 本工程采用的同步和缓冲策略
实际实现为单个解码工作线程,最多缓存4张待显示RGB565画面,并保留PTS。音视频压缩包通过FFmpeg demuxer顺序读取,以`avcodec_send_packet()` / `avcodec_receive_frame()`驱动两种解码器;EOF时发送空包刷新解码器,待输出耗尽后结束。
打开过程分为`avformat_open_input()`读容器头、`avformat_find_stream_info()`分析并汇总流信息、严格规格检查、创建正式解码器及输出缓冲。仅读完MP4头时,轨道时长和初始化数据可能已经存在,但格式总时长及AAC profile仍未补齐;不能把这个中间状态直接判为输入不支持。流信息分析保留已经读入的压缩包,后续`av_read_frame()`仍会依次取得这些包,不丢掉开头的视频或声音。
流信息分析使用`probesize=262144`(256KiB)和`max_analyze_duration=1000000`(1秒媒体时间),并为探测时可能打开的解码器设置`max_pixels=57600`、`max_samples=2048`、`threads=1`。先检查已声明的编码、尺寸、采样率及声道,分析后再执行完整的H.264 Baseline、帧率、AAC-LC、尺寸和时长限制。这里的1秒是分析的媒体时间预算,不是整个函数必须在1秒墙钟时间内完成;256KiB也不是整个MP4头和所有文件访问的总字节硬上限。分析仍通过原自定义AVIO及interrupt回调响应停止或新播放请求,失败时统一清理字典、解码器、文件与存储租约。
探测选项通过`av_dict_set()`传入十进制字符串:像素上限用`snprintf(..., "%u", ...)`生成,另两项分别为`"2048"`和`"1"`。这里有意避开FFmpeg `av_dict_set_int()`内部的64位整数格式化,以兼容本SDK的精简libc;接口名中的"int"并不保证不经过`%lld`字符串格式化,相关联调记录见6.2。
有声音时每约50ms查询音频服务的`media_player_get_breakpoint()`,将PCM解码进度作为媒体主时钟;没有声音时使用`k_uptime_get_32()`单调时钟。音频服务报告的进度包含DSP/DAC缓冲延迟,当前没有逐个DMA采样位置回读,因此不能宣称采样级精度或保证固定毫秒唇音误差。若音频进度超过750ms不推进,会打印日志并回退到单调时钟。实际音画误差需要在开发板上测量。
待显示画面的PTS到期时才提交。队列后面还有画面、当前画面落后主时钟超过200ms时,只跳过它的**显示**,继续保留必要的H.264输入解码流程,避免破坏参考关系。暂停通过音频服务pause停止PCM输出,同时保留容器位置、解码参考帧、输入PCM和画面队列;恢复时调整时钟锚点。SDK音频pause会flush已经进入输出通路的PCM,因此暂停/恢复边界可能丢失一小段待输出声音,当前不保证采样级无损续播;精确无损暂停需要进一步设计DSP断点及输出缓冲恢复机制。暂停时长不计入音频写入和EOF排空超时。
解码器有独立受锁保护的显示帧,GUI通过`mp4_player_lock_frame()`取得后,在锁内复制到GUI自己的双缓冲,再释放锁。这样GUI绘制不会引用已经释放的解码frame或工作线程重用的内存。240×240时,4张视频队列 + 1张发布帧 + GUI的2张副本,RGB565本身合计约787.5KiB;还须加上YUV参考帧、FFmpeg上下文/静态表、压缩包、32KiB音频环形缓冲及32KiB工作栈。不能把单张画面112.5KiB理解为总内存占用。
- 代码结构、配置与二次开发接口
| 文件 | 职责 |
| --- | --- |
| `src/main/mp4_player.c` / `.h` | 工作线程、状态机、自定义AVIO、格式检查、H.264/AAC解码、RGB565/PCM输出与资源释放 |
| `src/launcher/mp4_view.c` / `.h` | 文件列表、触摸控制、GUI独立帧缓冲和定时刷新 |
| `src/main/system_app_shell.c` | `app mp4`串口命令入口 |
| `thirdparty/lib/ffmpeg/ffmpeg_mp4_registry.c` | 限制只注册MOV/MP4解复用、H.264与AAC解码器,禁用URL协议回退 |
| `thirdparty/lib/ffmpeg/ffmpeg_mem.c` | FFmpeg运行时分配适配到MEM_RES资源池 |
| `framework/display/memory/Kconfig` / `ui_mem_res.c` | 资源池容量配置、低区常规资源堆初始化及有效寻址窗口日志 |
| `application/bt_watch/boards/ats3089p_dev_watch.conf` / `Kconfig` / `linker.ld` | 物理PSRAM仍配置32MiB;应用只使用低16MiB有效寻址窗口,内含6MiB MEM_RES资源池 |
| `tools/mp4/mp4_tool.py` | PC素材准备、规格检查与测试片合成 |
开启`CONFIG_APP_MP4=y`,由Kconfig选择`CONFIG_FFMPEG`。该模式使用新增独立播放器,不编译旧的FFmpeg仅视频`ffdecode.c`演示。原有AVI视频表盘播放器继续保留。构建可通过工程现有命令:
```powershell
cd zephyr
python -B build.py -n -b ats3089p_dev_watch -a bt_watch
```
FFmpeg内部只通过自定义AVIO回调调用Zephyr FAT的`fs_read/fs_seek`;不使用HTTP、网络媒体或宿主POSIX `open/read`。播放和目录扫描持有原有USB存储访问租约,MSC导出同一分区时拒绝并发操作。播放期间申请唤醒锁和高性能DVFS,统一在结束、停止或失败时释放。
Zephyr对第三方库采用whole-archive链接,只添加一个同名注册函数会与库中原实现重复定义。CMake因此在**构建目录的库副本**中移除`allcodecs.o`、`allformats.o`、`protocols.o`、`random_seed.o`,再链接本工程受限注册表和平台适配;原始供应商`.a`保持不变。该过程也避免默认注册表牵入所有其它编码器和不适用的宿主IO协议。音频断点查询还兼容SDK的PCM分支:部分版本会写入有效time_offset却返回`-EINVAL`,播放器先初始化为无效值,再验证是否实际写入有效进度,避免把未写入的0当成时钟成功。
SDK提供的是预编译FFmpeg静态库,工程内新增播放器、接口适配和注册表为C源码。替换FFmpeg库时应使用匹配的头文件、ABI、浮点约定和内存适配,并重新执行完整构建与播放验证,不能只拷入任意版本的`.a`文件。
| C接口 | 使用方法 |
| --- | --- |
| `mp4_player_play(path)` | 校验`/NAND:/...mp4`路径后异步提交播放;返回0表示成功入队,不代表文件已经解码成功 |
| `mp4_player_pause()` / `mp4_player_resume()` | 异步暂停/继续,查询状态确认执行结果 |
| `mp4_player_stop()` | 异步停止;等待状态停止后再复用有关音频/存储资源 |
| `mp4_player_get_status(&status)` | 复制受锁保护的状态快照:路径、状态、错误、进度、时长、尺寸、音轨和帧计数 |
| `mp4_player_list(paths, capacity)` | 在板内NAND根目录扫描MP4文件,受存储租约保护 |
| `mp4_player_lock_frame(&frame)` / `mp4_player_unlock_frame()` | 返回true后必须解锁;只允许锁内复制像素,不能把`pixels`指针直接交给异步GUI |
状态依次为`MP4_OPENING`、`MP4_PLAYING`,可进入`MP4_PAUSED`,最终为`MP4_FINISHED`、`MP4_STOPPED`或`MP4_ERROR`。当前没有对外提供进度拖动/seek接口;显示时间不代表支持任意跳转。扩展seek时需定位前方关键帧、刷新H.264/AAC状态和音视频队列,再重新建立时间基。
新增FFmpeg静态表和工作栈需要真实RAM空间。本轮将固定系统区及6MiB MEM_RES资源池统一限制在低16MiB有效寻址窗口,保留10个UI帧缓冲;原来常驻的AEC/A2DP/通话诊断快照改为采集时按需申请,避免空闲时占用大块PSRAM。布局与启动异常处理见6.1,最终占用以完整构建的链接map和运行时分配情况为准。队列、压缩包、图像和GUI副本采用有界分配,不加载整个视频到RAM。
- 常见问题与定位
| 现象 | 检查 / 处理 |
| --- | --- |
| 找不到ffmpeg或ffprobe | 将FFmpeg的bin加入PATH,或使用两个完整路径参数 |
| `Unknown encoder libx264` | 当前FFmpeg没有该编码器,更换包含libx264的构建 |
| 输出已经存在 | 默认保护原文件;换输出名,确认后使用`--overwrite` |
| `inspect`拒绝Main/High/B帧 | 用`prepare`重新转码,修改扩展名无效 |
| U盘根目录有文件,板端找不到 | 确认复制的是板内MSC磁盘,已安全弹出并恢复`/NAND:`挂载;先用ASCII文件名 |
| 有图像但没有声音 | 先检查inspect报告是否含AAC轨,确认不是`--no-audio`;再检查系统音量和音频资源冲突 |
| 声音正常但视频慢 | 先改为`--size 160 --fps 5`对比;检查CPU、显示负载和文件码率 |
| 图像花屏或颜色异常 | 检查YUV格式、stride、RGB565转换及缓冲使用寿命 |
| 播放一段时间后卡顿 | 检查包/帧是否及时unref、音频队列是否饥饿、FAT读取和日志是否阻塞 |
| 拷贝后文件损坏 | 停止播放后连接MSC;电脑安全弹出;禁止双方同时写FAT |
| 开机欢迎页退出时崩溃,还未进入MP4播放 | 参见6.1的资源链表与PSRAM I/O诊断;先确认烧录了本轮完整固件并检查`psram-io`日志 |
| 开机加载`adMUSIC.dsp`后提示`media`线程`Stack overflow` | 参见6.3;这次是公共音频服务栈不足,不能通过关闭栈哨兵或重新转码MP4解决 |
| 刚执行播放就`error=-134 time=0 decoded=0` | `-134`是本SDK的`-ENOTSUP`;先查`OPEN_FAIL stage`和`app mp4 status`的`detail`。1511版的随附合格样片也会因未汇总流信息而误拒绝,原因与修复见6.2 |
| `Protocol name not provided` | 自定义AVIO使用空URL时的协议优化提示,本次不是致命错误;继续看后续`OPEN`或`OPEN_FAIL/ERROR`,不要为消除此提示启用网络协议 |
原有AVI视频表盘使用MJPEG JPEG硬件解码和ADPCM音频,与本次H.264/AAC路径不同。MJPEG + AAC-LC可以被FFmpeg封装成真正MP4,但本功能明确使用上表H.264规格;PCM16能否封装MP4还受FFmpeg版本和规范扩展影响,不采用"输出MOV再改名MP4"的方式绕过。
6.1 2026-10-26启动异常:欢迎页位图链表被字体数据覆盖
`log_26_10_26.txt`记录了13:01、13:45以及14:13的启动异常。前两次在欢迎页`view 36`失焦、退出时,`ui_service`触发`Unaligned memory access`;13:45运行的是已经加入NAND中转的`1.00_2610261333`,仍然出现相同类型的节点损坏。14:13运行的是随后尝试高区无缓存资源堆的`1.00_2610261404`,设备依然停止在开机界面。三次均尚未开始播放MP4,不能将其归为视频解码失败。
使用**与该次崩溃匹配的ELF和map**解析,`PC=0x101ba1ac`对应`_free_resource_bitmap_buffer()`遍历位图链表时读取`item->addr`,上层是`res_manager_free_bitmap_data()`,触发动作是欢迎页释放Logo图片。后续重新构建会改变机器码地址,应重新用相同版本的ELF解析,不能直接套用此PC地址。
`buf_block_t`位图节点占64字节,`addr`字段位于节点偏移32,`next`字段位于偏移60。寄存器与文件内容给出以下对应关系:
| 项目 | 13:01故障的值 | 判断依据 |
| --- | --- | --- |
| 待释放Logo像素地址 | `0x38eb64c0` | 传给释放函数的指针 |
| 遍历经过的Logo节点 | `0x38eb6480` | 恰好是像素地址前64字节,与节点布局一致 |
| 节点`addr`字段 | 地址`0x38eb64a0`,内容`0x696e7507` | 正常应指向Logo像素,而此值是字体字节`07 75 6e 69` |
| 节点`next`字段 | 地址`0x38eb64bc`,内容`0x43434236` | 被作为下一个节点使用,导致未对齐访问 |
| 字体中相同字节 | `vfont.ttf`偏移`0x5bf140`及`0x5bf15c` | 两处内容和28字节间距均精确吻合,属于`post`表中的字形名称数据 |
因此,**已确认的是Logo节点内容被字体文件数据覆盖,释放操作只是暴露了已经损坏的链表**。正确修复需要找出覆盖写入路径,不能在释放函数中简单忽略非法指针;后者会隐藏内存破坏,并可能留下资源泄漏或后续随机崩溃。
13:45复现时,像素地址变为`0x38eb7500`、节点地址变为`0x38eb74c0`;损坏值仍是`addr=0x696e7507`、`next=0x43434236`。对应当前版本的`PC=0x101ba390`,仍位于相同资源释放路径。地址随链接布局移动,覆盖内容和崩溃特征一致,说明仅增加NAND中转并没有解决这次故障。
该版链接映射中,UI帧缓冲、MEM_RES资源池、SDFS缓存和字体缓存之间没有静态地址重叠;资源池初始化机器码使用的地址、大小也与数组一致。字体没有启用整文件内存映射,而是通过文件接口读取。尚未发现FFmpeg的`jx_malloc/jx_free`覆盖公共`malloc/free/memcpy`符号。这些检查排除了几种直接原因,但不能代替真实硬件上的I/O验证。
该板已按具体ATS3089P规格确认PSRAM为**256Mbit,即32MiB**,保持`CONFIG_PSRAM_SIZE=32768`;位容量换算成字节容量需要除以8。不能凭地址相差16MiB就认定容量配置错误。
前两版重点怀疑PSRAM高低地址映射、缓存标签及写回路径。例如`0x39eb64a0`与坏节点字段`0x38eb64a0`相差16MiB,结合SDFS缓存布局和字体源偏移,可以构造出吻合的缓存块位置。**这些地址对应关系本身尚不足以证明具体的缓存别名或DMA覆盖机制**;14:13加入写缓冲排空及持久读回的新探针,才进一步区分出实际存储通路与仅驻留在缓存中的结果。
已经核查SDK的实际复制实现:`CONFIG_LIBC_STRING_BROM=y`使`memcpy/memmove`通过ROM接口表调用`librom.hex`中的代码。ROM `memcpy`入口为Thumb地址`0x716e`,32字节批量复制助手为`0x70f4`,核心是CPU的`ldmia/stmia`,尾部使用字节读写;`memmove`入口`0x71dc`为前向或后向字节复制。反汇编未发现DMA/SDMA调用、24位软件地址掩码或清除bit24的操作,新增FFmpeg也没有改变该libc选项。因此不能将本次故障归因于"memcpy内部用DMA或软件截断了高位";CPU通过缓存窗口访问时的硬件映射仍需独立验证。
前一轮已在SPI NAND驱动中增加**4096字节、64字节对齐、位于低16MiB PSRAM**的中转缓冲。高地址、跨越16MiB边界或未按64字节对齐的PSRAM读写分块经过中转区;CPU负责它与最终缓冲之间的复制,驱动锁保证复用安全,并维护中转区的缓存。该保护覆盖SDFS、FAT、USB MSC及MP4共用的NAND通路,继续保留;13:45复现已说明它本身不足以修复上述启动异常。
13:45日志还包含旧版探针结果:
```text
psram-io CPU 16MiB address probe=independent low=0x3c006240 high=0x3d006240; NAND staging=0x38006240 size=4096
```
这里的两处地址均为**无缓存别名**。`independent`只记录当时两处被测地址的读回结果,不能据此证明高半区缓存地址`0x39...`与低半区`0x38...`不存在标签冲突,也不能替代写缓冲排空和持久读回验证,更不代表整片PSRAM或播放通过。
`1.00_2610261404`曾尝试将静态系统数据留在低16MiB缓存区,并把整个16MiB MEM_RES资源池放到`0x3d000000..0x3e000000`无缓存窗口。**14:13的实板结果已否定这次隔离方案**:高区无缓存访问仍然折回低区,不能因为两段CPU地址不同就视作独立物理存储。该版构建和ARM仿真通过,不等于开发板启动通过。
14:13:38的原始探测输出如下:
```text
psram-io 16MiB probe uncached=ALIAS_OR_FAULT cached=independent persisted=ALIAS_OR_FAULT; low=0x3c006240 high=0x3d006240; NAND staging=0x38006240 size=4096
psram-io values uncached=a55a33cc/a55a33cc cached=55aacc33/a55a33cc persisted=a55a33cc/a55a33cc
psram-io SPI1_CTL=0x30000038 DDR_MODE_CTL=0x80560709 CACHE_CTL=0x0002cd21 MR8=0x00000043
psram-io MEM_RES mode=high-uncached base=0x3d000000 size=16777216
```
探针先向低地址写入`0x55aacc33`,排空写缓冲,再向相差16MiB的高地址写入`0xa55a33cc`并再次排空。无缓存读回的两端均变成后一个值;随后通过两个缓存地址写入时,两者暂时各自正确,但写回、失效缓存后,两端持久读回又相同。这说明**当前SPI1到PSRAM的实际存储通路未保持该高低地址独立,缓存内的短暂独立结果掩盖了别名**。这比旧版单次`independent`更有诊断意义,也解释了只移动资源堆至高区无缓存地址仍然失败。
SDK的`zephyr/soc/arm/actions/leopard/spicache.h`定义了64MiB大小的CPU缓存/无缓存窗口;`spicache.c`中的`cache_to_uncache()`只是保留偏移并加上窗口差值。**CPU可解码的窗口大小并不是已经验证的PSRAM可用容量**。`CONFIG_PSRAM_SIZE`描述容量并参与链接,现有`soc_psram.c`不会根据它自动更改存储寻址宽度。当前构建也未启用NAND/EMMC启动的SPI0高区XIP映射,不能将此次覆盖归因于该功能占用高区。
实板`DDR_MODE_CTL=0x80560709`、`MR8=0x43`与P系列预编译初始化相符,但仅凭这些值仍不能证明具体寻址位的含义。现阶段没有寄存器手册证据能确定是控制器地址位配置、PSRAM型号/密度识别还是其他封装映射差异;**不将其表述为芯片只有16MiB,也不认定必然烧错C系列启动程序,更不猜测修改寄存器位**。
本轮修复使用低16MiB独立地址窗口,物理容量配置仍保持32MiB。板级配置如下:
```text
CONFIG_PSRAM_SIZE=32768
CONFIG_APP_PSRAM_ADDRESSABLE_SIZE=16384
CONFIG_UI_MEM_NUMBER_BLOCKS=10
CONFIG_UI_RES_MEM_POOL_SIZE=6291456
```
| 区域 / 配置 | CPU地址范围或容量 | 用途 |
| --- | --- | --- |
| 物理PSRAM描述 | 32MiB,`CONFIG_PSRAM_SIZE=32768` | 与ATS3089P规格一致,不因本次规避措施改写芯片容量 |
| 应用有效寻址窗口 | `0x38000000..0x39000000`,右端不包含 | 全部静态数据、线程栈、10个UI帧缓冲、SDFS/字体缓存及MEM_RES数组 |
| MEM_RES | 6MiB,6,291,456字节;位于上述低区内 | UI资源、FFmpeg动态对象、RGB565/PCM,以及按需创建的诊断快照 |
| 高16MiB地址 | 应用不分配、不建立资源堆 | 等明确寻址配置并通过实板独立性验证后再考虑开放 |
活动应用链接脚本`application/bt_watch/linker.ld`改用`CONFIG_APP_PSRAM_ADDRESSABLE_SIZE`限制`PSRAM`区域,链接断言同时约束有效窗口不得超过物理容量、所有应用PSRAM段不得越过`0x39000000`、NAND中转区须落在低区。删除高区无缓存资源堆方案,MEM_RES恢复使用低区常规缓存地址;6MiB是链接预留池容量,不代表启动时已分配6MiB对象。物理32MiB与应用保守使用16MiB分别描述芯片规格和本次已经验证的寻址边界,并不矛盾。
为把现有功能放进此窗口,原来合计5,644,288字节的AEC、A2DP及stock通话静态诊断快照改为采集时从MEM_RES申请,PCM容量按实际请求秒数计算,A2DP的SBC区保留512KiB上限。平时只保留管理信息,不再常驻最大时长的快照数组;采集入口在线程上下文预分配,IRQ/音频回调只写入已分配的缓冲,禁止在中断中申请或释放。采集完成后保留`ready`快照供导出,执行对应`clear`才释放;申请失败返回`-ENOMEM`,不能继续使用空指针或覆盖其他对象。
| 诊断快照 | 本次申请的容量,不含分配器开销 | 最大请求时长及容量 |
| --- | --- | --- |
| AEC三路16kHz/S16 | `秒数 × 16000 × 3 × 2` | 20秒,1,920,000字节 |
| stock通话双路16kHz/S16 | `秒数 × 16000 × 2 × 2` | 20秒,1,280,000字节 |
| A2DP SBC + PCM | `524288 + 秒数 × 48000 × 2 × 2`,PCM按48kHz双声道上限预留 | 10秒,2,444,288字节 |
诊断采集与MP4共享6MiB资源池,需控制峰值。测试播放前先导出并清理不再使用的快照,采集时按需选择较短时长;长快照与解码器参考帧、GUI图像同时保留可能使申请失败。第7.1.1节的短样片预算约3.4MiB,不包括全部UI和堆碎片,也不能保证任意视频、任意诊断组合都能同时运行。资源不足应明确报错,不允许从未验证的高区补空间。
在相应采集已停止、快照已导出且不需继续保留后,普通串口可分别执行以下命令释放诊断快照;正在采集或导出时应先完成/停止原操作,`clear`会返回忙而不会释放正在使用的缓冲。
```text
app aec dump clear
app a2dp_dump clear
app call_dump clear
```
| 修复文件 | 作用 |
| --- | --- |
| `zephyr/drivers/spinand/spinand_acts.c` | 中转读写、失败块不复制;分层探测uncached、cached及排空后结果,记录PSRAM相关寄存器 |
| `application/bt_watch/Kconfig` / `linker.ld` | 定义应用有效寻址容量,将所有PSRAM链接段约束在低16MiB;优先放置`.psram_dma.bss`并检查范围 |
| `framework/display/memory/ui_mem_res.c` | 使用低区常规资源堆,开机打印真实堆地址和大小 |
| `application/bt_watch/boards/ats3089p_dev_watch.conf` | 物理容量32MiB、有效窗口16MiB、MEM_RES 6MiB,保留10个UI帧缓冲 |
| `application/bt_watch/src/main/aec_capture.c` / `a2dp_dump.c` / `stock_call_dump.c` | 诊断快照按采集秒数动态申请,完成后保留到clear,IRQ中不申请/释放 |
| `zephyr/subsys/sdfs/sdfs_nand_sd.c` | 底层读失败向上传播、不推进文件位置、撤销失败缓存;可选SDFS中转模式按缓冲大小分块,避免小缓冲承接整块32KiB缓存 |
新探针分别比较无缓存地址读写、同时保留两个缓存标签时的读回,以及排空写缓冲/写回失效后经无缓存地址读取的结果;保存并恢复两处完整缓存行。日志格式如下,每个结果实际输出`independent`或`ALIAS_OR_FAULT`,地址和寄存器值以实板输出为准:
```text
psram-io 16MiB probe uncached=... cached=... persisted=...; low=... high=...; NAND staging=... size=4096
psram-io values uncached=.../... cached=.../... persisted=.../...
psram-io SPI1_CTL=0x... DDR_MODE_CTL=0x... CACHE_CTL=0x... MR8=0x...
psram-io high/low alias detected; application is bounded to low 16384 KiB, upper window unused
psram-io MEM_RES mode=cached-bounded base=... size=6291456; addressable=16384 KiB nominal=32768 KiB
```
预内核保留该探针,供继续核查初始化与厂商排查;它在中断锁保护下保存两端完整缓存行,测试后排空写缓冲并恢复原值。出现已知的`ALIAS_OR_FAULT`时,本轮日志随后明确说明`application is bounded to low 16384 KiB, upper window unused`,表示应用已经执行低区限制,并非继续把高区当资源池。MEM_RES日志则同时给出`addressable=16384 KiB`和`nominal=32768 KiB`,便于核查生效配置。即使未来三项均独立,也只证明这次小范围探测,需要继续实测完整UI和播放,不能直接扩展链接上限。
烧录后检查以下内容:
-
记录冷启动完整串口输出,核对`psram-io`中应用有效窗口和MEM_RES布局;堆应在`0x38000000..0x39000000`内,容量6,291,456字节,不能再出现高区资源堆`base=0x3d000000`。
-
如仍有读盘或UI异常,保存首次错误前后的日志和固件校验值;先检查存储/内存通路,再评估视频解码性能。
-
确认欢迎页正常退出、主界面可进入,然后测试`app mp4 list`及随附样片的播放、暂停、恢复、停止。启动恢复正常与MP4有声音有画面是两个需要分别记录的验证结果。
-
本轮使用下载工具烧录**普通完整包**`ats3089p_dev_watch_261026.fw`,更新包含`mbrec`在内的启动区及应用。用户已确认前几次均通过USB CDC IAP / `ota_firmware.bin`更新;板级`firmware.xml`中`mbrec`分区的`enable_ota=false`,因此该方式不会更新负责PSRAM初始化的启动程序。文件仍在原`_firmware`目录、沿用原日期文件名,应同时核对版本、修改时间及同目录manifest/SHA256,避免误烧同名旧包。这个更新边界并不能证明板上一定保留了旧C系列启动程序,也不能替代本轮完整烧录后的验证。
已完成以下ARM仿真回归,这些结果验证分块、边界和错误处理。1511版开机正常另有用户实板反馈支持,见6.2;1559版随后在公共媒体栈溢出,见6.3。仿真本身不证明芯片高区地址可用,也不代表整板冷启动及音视频播放已经通过实板验证。
| 回归 | 场景 / ARM断言 | 结果 |
| --- | ---: | --- |
| SDFS直读 | 40 / 1,085,937 | 通过 |
| SDFS可选中转模式 | 167 / 3,367,432 | 通过 |
| SDFS合计 | 207 / 4,453,369 | 通过 |
| SPI NAND驱动中转 | 44 / 8,718 | 通过 |
| 预内核地址探测 | 4场景,每场景128项事件检查:独立映射、物理地址别名、缓存别名分别写回低区/高区 | 通过;两处完整缓存行原值恢复、写缓冲排空及缓存/IRQ顺序均已检查,实际芯片结果仍须冷启动日志确认 |
| 1404历史固件MEM_RES资源堆 | 76项检查、16,056,485字节数据验证 | ARM仿真通过,但14:13实板失败;该仿真不模拟真实高低地址别名,不能作为高区可用的证据 |
| 本轮1620固件MEM_RES资源堆 | 77项检查、6,099,928字节数据验证 | 最终ELF真实`sys_heap`及ROM复制指令通过,26个cached段及2,483个静态对象均位于低区;此项检查布局及堆,不模拟公共音频线程的中断时机 |
| 诊断快照动态内存 | 218,807项ARM断言:AEC 178、A2DP 217,908、stock通话721 | 三种最大时长、OOM回滚、ready保留/clear释放及实时回调不申请/释放均通过;分配器和平台服务由模拟器替代,未模拟硬件调度 |
在``根目录复现,依赖安装见7.1;NAND脚本默认还检查实际固件ELF中的中转区大小、对齐和地址:
```powershell
python -B zephyr/subsys/sdfs/tests/run_read_tests.py
python -B application/bt_watch/tests/mp4/run_spinand_io_tests.py
python -B application/bt_watch/tests/mp4/run_psram_probe_tests.py
python -B application/bt_watch/tests/mp4/run_resource_heap_tests.py
python -B application/bt_watch/tests/mp4/run_snapshot_memory_tests.py
```
结果分别写入`outdir/sdfs_read_tests/report.json`、`outdir/mp4_tests/spinand_io/spinand_io_report.json`、`outdir/mp4_tests/psram_probe/psram_probe_report.json`、`outdir/mp4_tests/resource_heap_report.json`和`outdir/mp4_tests/snapshot_memory/report.json`(均相对`application/bt_watch`)。只在完整构建前验证逻辑时可给NAND脚本加`--skip-firmware-check`;交付验收应去掉该参数,确认`firmware_placement_verified=true`。
6.2 2026-10-26播放立即失败:流信息未补齐导致`-134`
低16MiB有效窗口的`1.00_2610261511`完整包烧录后,用户已确认开机正常。最新`log_26_10_26.txt`中15:42可见系统正常运行,15:43:54执行`app mp4 play /NAND:/demo_240.mp4`,随后15:43:55出现`mp4 ERROR error=-134 time=0 decoded=0 dropped=0`。这是进入播放打开流程后立即拒绝,与6.1的欢迎页内存覆盖故障不同;启动恢复已经得到本次实板反馈,样片播放尚需修复后验证。
本SDK的`ENOTSUP=134`。修复前的`open_context()`在`avformat_open_input()`之后立即检查`AVFormatContext.duration > 0`,但该API主要负责打开容器及读取头信息,尚未保证所有汇总字段和解码参数都已建立。使用仓库同一`demo_240.mp4`、真实供应商ARM MOV/H.264/AAC库与Cortex-M33模拟器,在`avformat_open_input()`成功返回点读取对象,得到:
| 字段 | 仅读头后的实际值 | 含义 |
| --- | --- | --- |
| `avformat_open_input()`返回值 | 0 | 容器已成功打开 |
| `format->duration` / `start_time` | `AV_NOPTS_VALUE`,即`-9223372036854775808` | 尚未汇总,不能等同于文件时长为负 |
| 视频轨时长 / time base | 30,720 / `1/10240` | 实际为3秒 |
| 视频编码 / 图像 / 帧率 | H.264,240×240,10/1 fps;`avcC`开头`01 42 c0 0d` | 符合样片目标规格,profile_idc=66 |
| 音频轨时长 / time base | 48,000 / `1/16000` | 实际为3秒 |
| 音频采样率 / 声道 | 16,000 Hz / 1 | 符合目标规格 |
| AAC解码器profile | `AV_PROFILE_UNKNOWN`,即`-99` | 即使单独打开解码器也仍未知,流分析后才补为AAC-LC的`1` |
因此旧版在总时长这一项就必然返回`-ENOTSUP`,尚未解码任何画面;即使单独绕过此项,后续AAC profile校验仍会遇到未补齐的状态。样片的PC规格检查通过、真实解码器能够解出30个视频帧,并不能证明旧版生产打开逻辑正确:原有解码回归直接按轨道建立解码器,没有执行生产播放器的总时长及AAC profile拒绝条件,漏掉了这条集成路径。
本次在正式规格校验前增加有资源限制的`avformat_find_stream_info()`,补齐总时长、起始时间及音轨profile,再保留原有严格校验。探测限制与压缩包保留方式见4.2。没有通过修改样片扩展名、取消AAC-LC约束或把未知时长直接当作有效时长来规避错误。新增`META`、`STREAM`、`OPEN_FAIL stage=...`及终态`detail`,用于区分流信息未完成、真正不支持的输入、内存不足及输出通路失败;使用方法见2.4。
在补充真实生产入口回归时,又发现了探测选项与精简libc的兼容问题:`av_dict_set_int()`内部使用`PRId64`对应的64位格式字符串;当前nano libc把该格式转换成了错误的`"ld"`选项值,导致`max_pixels`等选项不能正确解析,AAC流信息仍可能丢失。原独立元数据测试使用`av_dict_set()`和常量字符串,因此没有触发此问题。正式修复改用上一节的32位`%u`格式及字符串选项,不依赖64位整数格式化;不能仅确认调用过`find_stream_info()`便认为生产打开流程已经正确。
对应新增真实生产联合回归直接纳入`mp4_player.c`,执行同一`open_context()`、`analyze_streams()`、正式解码器创建及`close_context()`,链接供应商ARM库和生产内存适配器,并在6MiB测试堆内预留GUI双份RGB565缓冲。它同时检查探测后的完整包数/帧数和资源释放,并用删除流分析调用的故障变体确认能够重现旧版`-134`,避免只测试一份相似算法而继续漏掉生产集成问题;复现方法及验证范围见7.1.2。
日志中的`Protocol name not provided, cannot determine if input is local or a network protocol...`来自FFmpeg对空URL的提示:本工程已经提供自定义AVIO并禁用宿主/网络协议,无法按URL协议名称自动选择某些访问优化。它不是这次`-134`的返回来源;真实错误来自后面的生产规格判断。确认修复应查看`META duration_us=3000000`、AAC的有效LC profile及`OPEN ... duration=3000 ms`,随后验证视频帧计数、图像、声音和自然结束,不能仅以提示消失作为通过条件。
6.3 2026-10-26再次开机停止:公共音频服务线程栈不足
1559版在16:14冷启动时再次停住,但最新日志已给出与6.1不同的明确故障:
```text
Version Name: 1.00_2610261559
psram-io MEM_RES mode=cached-bounded base=0x38881080 size=6291456; addressable=16384 KiB nominal=32768 KiB
read off=0x4790000, file=welcome.act
read off=0x4790000, file=adMUSIC.dsp
*** Stack overflow detected ***
Thread:0x38134910 media , SP: 0x38134a20, sp_b = 0x38134a28 orverride 8 bytes!
>>> ZEPHYR FATAL ERROR 2: Stack overflow on CPU 0
Fault during interrupt handling
Current thread: 0x38134910 (media)
```
线程统计同时显示`media stack size 1768 usage 1768 (100 %)`。用保存的1559故障ELF解析,`PC=0x101e664e`对应`check_thread_stack_overlow()`,经`z_check_stack_sentinel()`在中断退出路径发现越界;它是检测并报告故障的位置,不能误认为H.264/AAC解码器异常指令。此时正在播放开机`welcome.act`提示音、加载其`adMUSIC.dsp`,尚未执行`app mp4 play`,也没有进入新增MP4解码线程。低区MEM_RES配置已经生效,日志没有重现此前Logo节点被字体覆盖的特征。
结合匹配ELF解析和源码,可定位到公共启动音频的深层路径:
```text
media_service_main_loop
→ playback_service_open / playback_decoder_init
→ dsp_session_open / dsp_acts_request_image
→ load_dsp_image_from_file / load_scntable_from_file
→ printk / vprintk / cbuf_output
→ offline_log_capture / make_timestamp / append_decimal
```
这里既有媒体服务、DSP镜像加载及格式化日志的调用帧,又有离线日志时间戳转换的栈开销;异常发生在中断检查时,还须为CPU异常入栈及上下文处理保留余量。`orverride 8 bytes`只表示这次检查已经越界的量,不能据此认为只加8字节就能覆盖整个音频初始化与运行过程的最大栈需求。
1559与已能开机的1511版使用相同的`CONFIG_MEDIASRV_STACKSIZE=2048`;创建服务线程会占用其中的线程控制信息,实际报告可用栈为1,768字节。已经对比两个ELF中的媒体加载、`vprintk`、离线采集和时间戳函数:关键调用路径的函数机器码大小及入口栈分配一致,新增流分析逻辑尚未调用。现有证据支持公共媒体栈原本就缺少安全余量,1559这次启动在实际中断/调度时机下越过边界;不能将"1511有一次能开机"当作所有日志及中断组合均安全,也没有证据把本次直接归因于未调用的`avformat_find_stream_info()`。
修复在`application/bt_watch/boards/ats3089p_dev_watch.conf`显式设置`CONFIG_MEDIASRV_STACKSIZE=8192`,将公共媒体服务总分配从2KiB增至8KiB,按相同线程创建开销预计提供7,912字节可用栈。该调整增加6,144字节低区PSRAM静态预留,沿用低16MiB寻址窗口;栈监控、栈哨兵、6MiB MEM_RES及10个UI帧缓冲继续工作。选择足够余量是为了覆盖公共音频初始化、日志采集和中断路径,并非声称已经测得所有业务的最坏峰值;最终布局和版本以7.2的新构建记录为准。
新增MP4真实ARM联合回归使用模拟RTOS/FAT/音频设备API,它能够发现本次流信息和字典参数问题,但没有启动公共`media`服务线程,也未模拟真实中断时机,因此不能验证这个1,768字节栈是否安全。资源堆回归检查内存分配与链接边界,同样不执行整板冷启动。1559故障ELF/map/config及原始日志保留在`outdir/mp4_tests/boot_fault_261026_1559`,后续解析应使用对应版本,不能用扩栈后的ELF套用旧地址。
验证时先完整烧录新普通`.fw`并断电重启,记录带版本号的启动日志,确认欢迎提示音、主界面及存储初始化完成;然后查询线程栈使用量并重复冷启动、提示音、MP4播放/暂停/停止与离线日志导出。须检查`media`实际可用栈及峰值余量,同时保留异常检测。PC仿真通过、固件链接成功和未再次出现越界日志,分别是不同层面的验证,最终播放画面、声音和并发功能仍需实板记录。
当前固件已启用`CONFIG_KERNEL_SHELL`、`CONFIG_INIT_STACKS`及`CONFIG_THREAD_MONITOR`,普通串口可执行:
```text
kernel threads
kernel stacks
```
在启动完成及各项音频操作后分别保存输出,核对`media`可用栈为7,912字节,并记录已用量及剩余量;栈填充值统计反映本次启动以来触及的高水位,不能只看当前空闲时的SP。若仍有栈告警,应保存告警前完整日志和同版本ELF,不通过关闭`CONFIG_STACK_MONITOR`或哨兵检查隐藏故障。
- 验证记录
验证日期:2026-10-26。已完成的PC检查如下,不能将PC能解码视为板端已实测。
| 检查 | 结果 |
| --- | --- |
| Python单元测试 | 8项通过,覆盖损坏box/64位长度/EOF长度、MOV改名、fragment MP4、超规格视频/音频、包长/时长边界、覆盖保护 |
| 真实3秒样片 | `ftyp=isom`,`moov`在`mdat`前;240×240、10fps、H.264 Constrained Baseline level1.3、无B帧、AAC-LC 16kHz单声道 |
| 样片大小 / 包 | 82,689字节;78个音视频压缩包,最大包5,861字节 |
| 样片完整解码 | PC FFmpeg音视频解码到null输出成功 |
| SDK真实ARM库解码 | Cortex-M33模拟器执行生产registry/allocator及供应商MOV/H.264/AAC机器码:78包、30个240×240视频帧、47个AAC帧,共48,128个单声道16kHz采样;正常刷新并释放 |
| 真实ARM流信息语义回归 | 仅读头及仅补URL时总时长仍为`AV_NOPTS_VALUE`、AAC profile为-99;分析后总时长3,000,000微秒、profile=1;探测缓存的78包全部继续解码,无丢包 |
| 真实生产打开/关闭联合回归 | 完整生产`open_context/close_context`及供应商ARM库通过,6MiB堆内含GUI预留;78包、30视频帧、47AAC帧、48,128采样;删除流分析的故障变体精确重现`Open duration failed (-134)`;见7.1.2 |
| 公共媒体DSP加载与日志栈预算 | 最终及1559 ELF的真实ARM加载/日志下层均通过,19次读取77,164字节;下层动态峰值1,332字节,结合上层CFI及基本异常预算共1,776字节,1620可用栈7,912字节;见7.3及其仿真边界 |
| 解码内存峰值 / 高位指针 | 相同真实ARM解码分别使用`0x38000000`与`0x3d000000`堆,峰值2,558,040字节、1486次分配全部释放,视频/音频输出SHA256逐字节一致;见7.1.1 |
| ARM浮点兼容性 | 最终ELF为ARMv8-M mainline,HardFP use为SP only;反汇编检查FP64指令数0;显式使用Cortex-M33模拟模型 |
| 生产内存适配器回归 | 8,893项ARM断言通过,覆盖对齐、扩缩容、失败保留及溢出处理 |
| 生产播放器状态机回归 | 73项ARM断言、7场景通过,覆盖容器/AVIO、取消、音频时钟回退、采样时间裁剪、暂停超时排除、EOF双解码器刷新及资源释放 |
| prepare真实转码 | 样片转为160×160、5fps、无音轨MP4,通过inspect |
| 覆盖保护 / 不支持格式 | 拒绝覆盖原样片;MJPEG+AAC的真正MP4被本H.264规格检查拒绝 |
| CLI与真实媒体检查 | 6项检查全部通过,其中包含8项单元测试整体运行 |
| 机器可读记录 | `outdir/mp4_tests/pc_checks.json`,`demo_report.json`,`silent_report.json`,`arm_decode_report.json`,`open_metadata_report.json`,`player_open_report.json`,`media_stack_report.json` |
样片SHA256:`4e9adb9d44aeb8305d36ec8eee6d120c53ab3042ec63634a3b3904fe366f016b`。可运行以下命令重复单元测试:
```powershell
python -B -m unittest discover -s application/bt_watch/tools/mp4 -v
```
本机PC转码使用FFmpeg 4.0.git(含libx264/AAC),ffprobe来自5.1.0分发包;工具也可使用更新且包含相同编码器的FFmpeg。可执行文件未加入工程仓库。
7.1 复现真实SDK ARM解码检查
此检查无需开发板,但需要工程SDK的`arm-none-eabi-gcc`、配套`readelf/objdump`以及Python的Unicorn / pyelftools:
```powershell
python -m pip install "unicorn>=2.1,<3" pyelftools
python -B thirdparty/lib/ffmpeg/tests/run_decode_tests.py
python -B thirdparty/lib/ffmpeg/tests/run_port_tests.py
python -B application/bt_watch/tests/mp4/run_engine_tests.py
```
脚本优先从PATH找编译器,找不到时使用本工程常用SDK路径`C:/Actions/ZEPHYR_SDK/1.10/.../arm-none-eabi-gcc.exe`;也可传`--compiler "完整路径"`。Python依赖除常规site-packages外,还会查找已有测试依赖目录`outdir/offline_log_tests/deps`和`outdir/ble_record_tests/deps`。
脚本自动从仓库样片生成内存文件字节,编译`thirdparty/lib/ffmpeg/tests/test_decode.c`,链接**真实**供应商库与生产`ffmpeg_mp4_registry.c` / `ffmpeg_mem.c`,然后在显式Cortex-M33模型中执行。样片字节inc、ELF、map和JSON结果都写入`application/bt_watch/outdir/mp4_tests`,无需把生成文件放入源码目录。测试用内存AVIO和宿主堆替代FAT/硬件;不验证真实NAND速度、显示驱动或DSP输出。
原始AAC解码输出包含尾部padding,因此47×1024=48,128个采样比3秒×16,000=48,000稍多;播放器依据媒体时间裁剪/排空,不能把压缩帧总采样数直接当作精确容器时长。一次宿主仿真运行耗时约2.4~3.4秒,只表示PC模拟器用时,**不代表开发板解码速度或实时帧率**。
7.1.1 解码动态内存峰值与高位指针验证
在``根目录运行:
```powershell
python -B thirdparty/lib/ffmpeg/tests/run_decode_memory_tests.py
```
默认先重建并运行上一节的真实ARM解码测试,然后重新执行两次,将测试平台`_sbrk()`提供的16MiB堆分别映射到`0x38000000`和`0x3d000000`。供应商MOV/H.264/AAC、生产`ffmpeg_mp4_registry.c` / `ffmpeg_mem.c`和ARM libc的`malloc/free`均执行实际机器码;宿主钩子只统计分配/释放,并在帧交付点读取输出进行哈希。已有匹配的`decode_probe.elf`时可加`--skip-build`跳过重建,不应使用它复验已经更改的解码代码。
脚本从ELF的DWARF读取`AVFrame`字段偏移,对30帧YUV420P的实际可见行和47帧单声道float32音频平面计算SHA256,不把图像stride填充区计入结果。报告写入`application/bt_watch/outdir/mp4_tests/decoder_memory_report.json`,含样片/ELF校验值、每阶段峰值、最大请求和最终剩余分配数。
| 指标 | 低区地址堆 | 高区无缓存地址堆 |
| --- | ---: | ---: |
| 堆起始CPU地址 | `0x38000000` | `0x3d000000` |
| FFmpeg请求存活峰值,含生产适配器分配头/对齐余量 | 2,558,040字节 | 2,558,040字节 |
| 底层ARM libc堆已提交空间 | 2,568,352字节 | 2,568,352字节 |
| 最大一次底层分配请求 | 748,119字节 | 748,119字节 |
| 分配次数 / 释放次数 | 1486 / 1486 | 1486 / 1486 |
| 最终存活分配数 | 0 | 0 |
| 已检查视频 / 音频 | 30帧 / 47帧、48,128采样 | 30帧 / 47帧、48,128采样 |
两种地址的输出校验完全一致:
| 输出 | 两次相同的SHA256 |
| --- | --- |
| YUV420P可见图像字节 | `cd23165bbfc881c4f8d0f0420d69891c9f376a420fbacdffd3e743f8f87aab90` |
| 单声道float32音频字节 | `5411c99b8b3d5b52c9021d5d556b4b63d9fe5ded8cc9dc2b64c9e8a23b9285e2` |
float32是AAC解码器输出,尚未进行播放器的S16 PCM转换。高区测试实际分配指针位于`0x3d000008..0x3d2730a0`,完整解码、输出比对和释放都成功,支持这条执行路径没有截断指针高位的结论。
将样片峰值与播放器源代码中的独立缓冲相加,可得到一个动态内存预算:
| 组成 | 字节数 |
| --- | ---: |
| FFmpeg样片解码请求峰值 | 2,558,040 |
| 4张排队帧 + 1张发布帧,240×240 RGB565 | 576,000 |
| GUI的2张独立RGB565副本 | 230,400 |
| PCM环形缓冲 | 32,768 |
| 上述合计 | **3,397,208** |
后面三项是根据生产源码计算的预算,不是该解码测试实际创建的界面/音轨。合计还没有包括其余GUI资源、播放器控制对象、堆管理/碎片及固定静态表/栈;也不能据此把MEM_RES缩到3.4MiB。测试素材只有3秒,更长MP4的索引、不同H.264参考帧需求等可能进一步增加峰值。
这组3,397,208字节预算来自未执行生产流信息分析的解码基线。新增流分析后,7.1.2直接运行生产打开及解码流程的请求峰值为4,176,386字节,包含相同GUI预留及播放器自身缓冲;评估当前版本应使用新的联合回归数据,并继续为其他UI资源与碎片保留空间,不能沿用旧预算视作当前播放器的完整峰值。
该测试的底层堆是ARM libc,板端MEM_RES使用`sys_heap`,两者元数据和碎片开销不同。模拟器也将两个地址窗都看作普通RAM,**输出一致仅验证本次算法执行及32位指针使用,不模拟芯片缓存别名、DMA一致性、物理PSRAM容量或uncached访问速度**。14:13实板已证明这项测试不能验证高区独立可用;本轮生产固件仅使用低16MiB窗口,实际解码性能仍须按照6.1和7.2完成实测。
7.1.2 流信息与真实生产打开流程回归
在``根目录运行,编译器及Python依赖与7.1相同:
```powershell
python -B thirdparty/lib/ffmpeg/tests/run_open_metadata_tests.py
python -B thirdparty/lib/ffmpeg/tests/run_player_open_tests.py
```
第一项记录同一样片在仅读头、流信息分析后,以及只补文件名但不做流分析时的字段,确认`AV_NOPTS_VALUE`总时长与未知AAC profile的变化,并继续完整消费探测时缓存的压缩包。结果写入`outdir/mp4_tests/open_metadata_report.json`;该测试用于定位FFmpeg各阶段的元数据语义,不能单独代替生产入口测试。
第二项直接编译生产`mp4_player.c`,真实执行文件打开、规格校验、解码器初始化、帧/PCM缓冲申请与释放;仅FAT、RTOS及扬声器设备API由测试替身提供。测试预留230,400字节GUI双缓冲,并将底层堆硬限制为6,291,456字节,检查正式打开后仍可取得全部78个压缩包、30个视频帧、47个AAC帧和48,128个采样,最终所有分配及文件、存储、音频句柄均释放。故障变体仅移除生产流分析调用,预期以`Open duration failed (-134)`失败,从而验证本回归能够抓住本次遗漏。
本轮报告结果如下;它们是PC上的Cortex-M33仿真结果,运行耗时不代表开发板性能:
| 指标 | 正式生产入口 | 移除流分析的故障变体 |
| --- | ---: | ---: |
| 打开返回值 / 诊断 | 0;时长3,000ms、240×240、AAC-LC | -134;`Open duration failed (-134)` |
| 打开阶段请求存活峰值 | 3,546,141字节 | 317,352字节 |
| 解码阶段请求存活峰值 | 4,176,386字节 | 未进入解码 |
| 底层libc堆提交量 | 4,256,160字节 | 335,992字节 |
| GUI预留,已计入峰值 | 230,400字节 | 230,400字节 |
| 分配 / 释放次数 | 1894 / 1894 | 102 / 102 |
| 最终存活分配数 / 申请失败数 | 0 / 0 | 0 / 0 |
| 宿主仿真执行耗时 | 2.109秒 | 0.188秒 |
| 判定 | 通过 | 通过:按预期抓住故障 |
生产源代码SHA256为`314176b6a84c1f912259822a997ec85ed65af9a1ab654f3b018910247fcf9c9a`,两个测试ELF均为HardFP SP only、FP64指令数0。正式入口完整解码30视频帧/47音频帧,关闭时文件、存储租约、扬声器所有权、PCM流、播放器句柄及唤醒锁均已释放。
报告写入`outdir/mp4_tests/player_open_report.json`,包含生产源代码/样片/测试ELF校验值、打开及解码阶段请求峰值、底层libc堆提交量及分配/释放计数。堆峰值包含播放器五张RGB565缓冲、PCM环形缓冲、FFmpeg对象与GUI预留,底层libc元数据和对齐额外开销反映在提交量中。该联合回归使用测试平台的ARM libc堆,不能替代板端`sys_heap`碎片、真实GPU/LCDC/DSP、PSRAM缓存一致性或实时播放速度验证;最终固件资源堆和链接边界另按7.2检查。
7.2 完整固件构建与资源占用
2026-10-26本轮流信息分析、探测选项兼容及公共媒体服务扩栈修复已完成整工程构建与打包。普通完整包`ats3089p_dev_watch_261026.fw`的版本为**`1.00_2610261620`**,大小25,488,896字节,文件修改时间为**2026-10-26 16:20:04**;同轮普通raw、debug、raw_debug及OTA均为1620。1559版虽已修复流分析,却在16:14实板启动时暴露公共媒体栈不足,故本轮替换为1620包。1511版能开机但播放`-134`,1404/1405高区资源堆方案启动失败;这些历史故障分别见6.1~6.3。下表来自1620版最终链接结果,包含链接预留的6MiB资源池及扩容后的媒体服务栈,不等于运行时实际已分配量。
| 链接区 | 本轮占用字节数 | 区域容量字节数 | 使用率 |
| --- | ---: | ---: | ---: |
| FLASH | 3,387,228 | 7,041,024 | 48.11% |
| SRAM | 932,608 | 932,864 | 99.97% |
| PSRAM,应用低区cached,内含6MiB MEM_RES | 16,245,920 | 16,777,216 | 96.83% |
| SHARE_RAM | 20,948 | 20,991 | 99.80% |
物理PSRAM配置保持32MiB,应用链接有效窗口限制为16MiB。所有PSRAM段的最终链接终点为`0x38f7e4a0`,低于`0x39000000`,该窗口尚余531,296字节链接空间;MEM_RES位于`0x38882880`、容量6,291,456字节,已经计入上表PSRAM占用,不能再重复相加。资源池的链接预留不等于全部已malloc使用,UI资源、FFmpeg动态对象与诊断快照在池内共享容量。10个UI帧缓冲保持不变,原5,644,288字节的诊断静态大数组不再常驻。
本轮显式配置`CONFIG_MEDIASRV_STACKSIZE=8192`,公共`media`服务总分配为8,192字节,实际可用栈为7,912字节;此前2,048字节总分配只提供1,768字节可用栈。相比1559版,PSRAM静态预留增加6,144字节,已计入表中,没有占用MEM_RES动态池,也未启用高16MiB地址。6.3记录了扩容依据及验证边界。
SRAM链接区只剩256字节,不能再随意增加静态大数组;FFmpeg动态图像及GUI副本使用MEM_RES池。SRAM占用包含要求放入SRAM的libm/libc代码,并非等量音视频缓存。NAND中转缓冲的最终链接地址为`0x38006240`、大小4096字节,满足64字节对齐及低16MiB限制。
最终ELF的SHA256为`2f053b20f33f3c47f063c2fce05d677478c5dc866ee2e0c8f90c59c17609219d`,资源堆回归记录为`outdir/mp4_tests/resource_heap_report.json`。测试执行匹配最终ELF的真实`sys_heap`及ROM复制指令,77项检查及6,099,928字节数据验证通过,覆盖分配、强制搬迁realloc、扩缩容、失败保留、64/4096字节对齐及释放后6,287,360字节大块复用;最高CPU数据检查地址为`0x38e81924`。检查同时核对26个cached段及2,483个静态对象均位于低16MiB,资源池与其他对象不存在重叠。最终ELF保持HardFP SP only,FP64指令数为0。
本轮布局检查显式考虑实板已发现的16MiB折叠别名,增加四个应拒绝反例:旧高区无缓存堆折叠后覆盖低区、静态cached段越界、静态对象侵入资源池、单个对象跨过低区末端;四项均被拒绝,且实际保存的1404历史ELF也被新版检查拒绝。这样可以发现上一版把两处CPU地址当独立物理空间的验收盲点。诊断快照回归另有218,807项ARM断言通过,详见第6.1节;这些测试仍不模拟真实芯片缓存、GPU/DMA或并发硬件调度。
**本轮1620版的公共媒体扩栈及播放修复仍待实板验证**。完整烧录本轮普通`.fw`后断电重启,先确认欢迎页正常退出、可进入主界面并检查`media`实际7,912字节可用栈及峰值余量,再执行`app mp4 play /NAND:/demo_240.mp4`,检查`META duration_us=3000000`及`OPEN ... duration=3000 ms`。随后确认样片有画面有声音、240×240/10fps持续播放、自然结束、暂停/恢复边界、停止/退出释放、MSC互斥、坏文件错误,以及实际CPU负载、堆峰值、丢显示帧和音画偏差。通过PC和ARM仿真不能替代这些硬件验证。
固件统一在 `application/bt_watch/outdir/ats3089p_dev_watch/_firmware`,沿用 `ats3089p_dev_watch_261026.fw`、`ats3089p_dev_watch_261026_debug.fw`、原有 raw 文件名和 `ota_firmware.bin`。同目录的 `ats3089p_dev_watch_261026_manifest.json` 和 `ats3089p_dev_watch_261026_SHA256.txt` 记录本轮文件校验、构建配置及验证结果,没有另建功能固件目录。
7.3 公共媒体服务DSP加载与日志路径栈预算回归
为补充7.1.2未启动公共`media`线程的验证边界,本轮增加针对故障启动路径的独立回归。在``根目录执行,Python依赖与7.1相同:
```powershell
python -B application/bt_watch/tests/mp4/run_media_stack_tests.py
```
脚本分别载入保存的1559故障ELF及1620最终ELF,在Cortex-M33模拟器中执行其中真实的`load_dsp_image_from_file()`、`load_scntable_from_file()`、ARM `printk/vprintk`、`offline_log_capture()`及时间戳转换机器码,输入使用工程的`adMUSIC.dsp`。文件读写、时钟、DMA发送和信号量调度使用有界替身;DSP实际运行、真实存储和RTOS完整调度没有启动。两版均成功读取19次、共77,164字节,输出8条DSP段加载日志、离线采集808字节,DSP入口为`0x8e`;真实执行的加载及日志下层调用链测得SP下降峰值为1,332字节。
脚本另外使用匹配1559故障日志中的8个上层返回地址,从该ELF的DWARF CFI读取活动帧大小。它们覆盖`z_thread_entry`、`media_service_main_loop`、`media_service_open`、`playback_service_open`、两层解码初始化、`dsp_open_global_session`及`dsp_acts_request_image`,合计408字节;这些上层帧是依据故障现场计算的预算,没有在本测试中动态执行。再显式预留32字节基本CPU异常帧及4字节对齐,得到以下对照:
| 项目 | 1559故障版 | 1620扩栈版 |
| --- | ---: | ---: |
| ELF中`media`栈数组总大小 | 2,048字节 | 8,192字节 |
| 线程控制信息占用 | 280字节 | 280字节 |
| 实际可用栈容量 | 1,768字节 | 7,912字节 |
| 真实执行的DSP加载与日志下层峰值 | 1,332字节 | 1,332字节 |
| 故障现场上层活动帧CFI预算 | 408字节 | 408字节 |
| 基本CPU异常帧及对齐预算 | 36字节 | 36字节 |
| 上述合计预算 | 1,776字节 | 1,776字节 |
| 可用容量减去合计预算 | **-8字节** | **6,136字节** |
这组预算解释了原1,768字节可用栈缺少余量,并核对了扩栈后最终ELF提供的容量。它与实板报告的越界8字节相符,但**不是在模拟器中完整重现同一次RTOS栈溢出**:上层帧来自CFI,异常入栈是显式预算,FPU惰性压栈、IRQ嵌套及真实调度时机均未模拟;6,136字节也只是相对本条预算的余量,不能视作所有音频业务的最坏栈峰值。实板仍须按照6.3运行`kernel stacks`并记录冷启动、提示音及播放操作后的高水位。
机器可读结果写入`outdir/mp4_tests/media_stack_report.json`,包含两版ELF和DSP镜像SHA256、必需函数的执行计数、每个上层返回点的CFI帧大小及测试边界。本轮宿主仿真耗时24.062秒;这是PC测试用时,不代表开发板加载DSP所需时间。报告匹配的最终ELF SHA256为`2f053b20f33f3c47f063c2fce05d677478c5dc866ee2e0c8f90c59c17609219d`。