TXW82x MP4 兼容 Opus 音频封装:从编码帧到标准音频轨的完整改造
TXW82x MP4 兼容 Opus 音频封装:从编码帧到标准音频轨的完整改造
一、提交概况
项目:F:\TXW82X_MP4\txw82x
分析提交:4ca5cd669e4c0e49189ed3570fab4c3bb9f7ef11
提交信息:1、mp4: 增加 Opus 音频兼容处理
本次提交修改 5 个文件,共增加约 300 行、删除约 93 行代码,涉及四层:
| 层次 | 文件 | 负责内容 |
|---|---|---|
| 编码层 | sdk/app/audio_media_ctrl/opus/opus_encode.c |
生成 Opus 帧并提供正确的帧时长 |
| 封装接口层 | sdk/app/mp4/mp4_encode.h |
定义音频类型、音频轨结构和写入接口 |
| MP4 实现层 | sdk/app/mp4/mp4_encode.c |
写入 Opus、dOps 和 Opus sample table |
| MSI 适配层 | sdk/lib/video/miniMP4/mp4_encode_msi2.c |
将 AAC/Opus 音频帧送入对应封装接口 |
| UI 调度层 | sdk/app/ui/mp4_recorder_ui.c |
选择编码器并管理音频资源 |
这不是单纯增加一个 if (opus) 分支,而是把原先“音频是否存在”的布尔模型改造成“音频类型可扩展”的完整链路。
二、改造前的限制
原 MP4 录制器只支持 AAC:
1 | 音频 ADC |
原实现有三个结构性限制:
MP4_open_init只接收audio_en,只能表示有音频或无音频,无法表达 AAC/Opus。- MSI 音频批处理固定跳过 7 字节,因此只能处理 AAC ADTS,Opus 裸帧会被错误截断。
- MP4
stsd固定生成mp4a/esds,即使输入数据来自 Opus,播放器仍会按 AAC 解析。
因此,Opus 支持必须同时解决“数据裁剪、写入接口、索引时长、MP4 音频轨描述”四个问题。
三、改造后的总体结构
改造后,编码器类型从 UI 一直传递到 MP4 核心:
1 | MP4_RECORD_AUDIO_CODEC |
无音频时传入 MP4_AUDIO_CODEC_NONE,不会创建音频轨;AAC 保持原有路径;Opus 使用独立的 sample entry 和数据写入函数。视频仍然沿用原 H.264 轨道逻辑,本次提交没有改变视频帧解析和视频索引流程。
四、Opus 编码帧时长修正
文件:sdk/app/audio_media_ctrl/opus/opus_encode.c
MP4 封装不仅需要音频数据,还需要知道每个 sample 持续多长时间。Opus 编码线程通过 AUDIO_INFO 传递 nsamples、samplerate 和 time_interval。
原代码把 FRAME_SIZE 重复用于换算,结果不能作为毫秒使用。本次改为:
1 | s->audio_info.time_interval = s->audio_info.nsamples * 1000 / s->samplerate; |
计算过程变为“采样数 ÷ 每秒采样数 × 1000 = 帧时长(毫秒)”。该值进入 durations[],再由 MP4 音频写入函数转换为 timescale 下的 duration,最终写入 stts,从而修复 Opus 音频轨播放速度和总时长可能不正确的问题。
五、MP4 核心接口重构
文件:sdk/app/mp4/mp4_encode.h、sdk/app/mp4/mp4_encode.c
5.1 用枚举替代音频布尔值
新增:
1 | typedef enum { |
mp4_key_msg 保存 audio_codec,初始化时只接受上述三种值。NONE 不建立音频轨,AAC 沿用 mp4a/esds,OPUS 建立 Opus/dOps。MP4_open_init 和 MP4_open_init_with_file 均由接收布尔值改为接收枚举,调用者必须明确指定音频类型。
5.2 复用音频 sample entry 结构
原 mp4_mp4a 结构重命名为通用的 mp4_audio_entry,再通过类型别名兼容旧代码:
1 | typedef mp4_audio_entry mp4_mp4a; |
AAC 和 Opus 共用基础字段布局,但分别写入 mp4a 和 Opus box。
六、MP4 音频轨格式
AAC 结构保持不变:
1 | stsd → mp4a → esds |
Opus 结构为:
1 | stsd → Opus → dOps |
新增 mp4_opus_write 写入 data reference、声道数、采样位宽和 time scale;新增 mp4_dOps_write 写入版本、输出声道数、pre_skip、输入采样率、输出增益和 channel mapping family。
本次实现使用单声道配置、版本 0、pre_skip=0、增益 0、mapping family 0。采样率来自 msg->audio_samplerate,未设置时回退到 8000 Hz。
在 _MP4_init 的音频 stbl 中,根据 msg->audio_codec 选择 Opus/dOps 或 mp4a/esds,视频轨和 mdat 公共结构不变。
七、Opus 数据写入与索引
新增两个接口:
1 | write_opus_data(...) |
单帧接口依次检查上下文、音频类型、音频轨和文件容量,写入 mdat 后更新音频 sample table。
批量接口接收连续数据和每帧的 sizes[]、durations[]:
1 | opus_buf = frame0 + frame1 + frame2 + ... |
数据只写入一次 mdat,然后按累计偏移逐帧更新 stts、stsz、stco、音频帧数量和累计 duration。临时索引缓冲区不足时先调用 mp4_sync,避免长时间录制导致 sample table 缓存溢出。
八、miniMP4 MSI 的 AAC/Opus 分流
文件:sdk/lib/video/miniMP4/mp4_encode_msi2.c
mp4_has_audio 从只接受 AAC_ENC 改为同时接受 AAC_ENC 和 OPUS_ENC。批处理函数根据编码器设置 audio_head_size:
| 编码器 | 头部长度 | 写入 MP4 的数据 |
|---|---|---|
| AAC | 7 字节 | data + 7,长度减 7 |
| Opus | 0 字节 | data 全部内容 |
每个 framebuff 的 AUDIO_INFO.time_interval 填入 durations[]。批量条件满足时调用对应的 batch 接口;条件不满足时退回逐帧写入,但仍按编码器选择正确函数:
1 | AAC → write_aac_data_batch / write_aac_data |
录制启动时,MSI 将编码器映射为 MP4_AUDIO_CODEC_NONE、MP4_AUDIO_CODEC_AAC 或 MP4_AUDIO_CODEC_OPUS。设置音频采样率后,只有 AAC 调用 get_aac_config 和 mp4_audio_cfg_init,Opus 不再使用 AAC 的 AudioSpecificConfig。
九、录制 UI 的配置和资源生命周期
文件:sdk/app/ui/mp4_recorder_ui.c
新增默认配置:
1 |
默认行为仍为 AAC;改为 OPUS_ENC 后,UI 创建 Opus 编码器并让 MP4 MSI 使用 Opus 音频轨。
状态成员由 aac_msi 改为通用的 audio_msi。启动流程是:创建编码器、增加 MP4 输出、传入音频类型初始化 MP4 MSI。任一步骤失败时,统一删除音频输出、反初始化编码器并释放 H.264/MP4 MSI。停止录制和退出页面复用同一清理路径,避免 Opus 模式遗留编码器资源。
十、端到端调用链
1 | ADC 采样 |
十一、改动后的三种录制模式
| 模式 | 编码器 | MP4 音频轨 | 数据处理 |
|---|---|---|---|
| 无音频 | 无 | 不创建音频轨 | 只写 H.264 视频 |
| AAC | AAC_ENC |
mp4a/esds |
去除 7 字节 ADTS 头 |
| Opus | OPUS_ENC |
Opus/dOps |
保留完整 Opus 裸帧 |
本次提交最终打通了“编码器选择、帧数据处理、MP4 box 描述、音频时间索引和资源回收”五个环节。Opus 数据不再被当作 AAC 裁剪,播放器能够依据 Opus/dOps 识别音频轨;AAC 和无音频路径保持原有行为,H.264 视频录制流程不受影响。