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 写入 OpusdOps 和 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
2
3
4
5
6
7
8
9
音频 ADC

AAC 编码器
↓ ADTS 帧(7 字节头部)
mp4_encode_msi2
↓ 去除 ADTS 头
write_aac_data / write_aac_data_batch

mp4a + esds 音频轨

原实现有三个结构性限制:

  1. MP4_open_init 只接收 audio_en,只能表示有音频或无音频,无法表达 AAC/Opus。
  2. MSI 音频批处理固定跳过 7 字节,因此只能处理 AAC ADTS,Opus 裸帧会被错误截断。
  3. MP4 stsd 固定生成 mp4a/esds,即使输入数据来自 Opus,播放器仍会按 AAC 解析。

因此,Opus 支持必须同时解决“数据裁剪、写入接口、索引时长、MP4 音频轨描述”四个问题。

三、改造后的总体结构

改造后,编码器类型从 UI 一直传递到 MP4 核心:

1
2
3
4
5
6
7
8
9
10
11
12
13
MP4_RECORD_AUDIO_CODEC

audio_encode_init(AAC_ENC / OPUS_ENC)

mp4_encode_msi2.audio_encode
↓ 映射
MP4_AUDIO_CODEC_AAC / MP4_AUDIO_CODEC_OPUS

MP4_open_init_with_file()

根据类型生成 mp4a/esds 或 Opus/dOps

根据类型调用 AAC 或 Opus 写入接口

无音频时传入 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 传递 nsamplessampleratetime_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.hsdk/app/mp4/mp4_encode.c

5.1 用枚举替代音频布尔值

新增:

1
2
3
4
5
typedef enum {
MP4_AUDIO_CODEC_NONE = 0,
MP4_AUDIO_CODEC_AAC,
MP4_AUDIO_CODEC_OPUS,
} mp4_audio_codec;

mp4_key_msg 保存 audio_codec,初始化时只接受上述三种值。NONE 不建立音频轨,AAC 沿用 mp4a/esdsOPUS 建立 Opus/dOpsMP4_open_initMP4_open_init_with_file 均由接收布尔值改为接收枚举,调用者必须明确指定音频类型。

5.2 复用音频 sample entry 结构

mp4_mp4a 结构重命名为通用的 mp4_audio_entry,再通过类型别名兼容旧代码:

1
2
typedef mp4_audio_entry mp4_mp4a;
typedef mp4_audio_entry mp4_opus;

AAC 和 Opus 共用基础字段布局,但分别写入 mp4aOpus 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/dOpsmp4a/esds,视频轨和 mdat 公共结构不变。

七、Opus 数据写入与索引

新增两个接口:

1
2
write_opus_data(...)
write_opus_data_batch(...)

单帧接口依次检查上下文、音频类型、音频轨和文件容量,写入 mdat 后更新音频 sample table。

批量接口接收连续数据和每帧的 sizes[]durations[]

1
2
3
opus_buf  = frame0 + frame1 + frame2 + ...
sizes = [size0, size1, size2, ...]
durations = [time0, time1, time2, ...]

数据只写入一次 mdat,然后按累计偏移逐帧更新 sttsstszstco、音频帧数量和累计 duration。临时索引缓冲区不足时先调用 mp4_sync,避免长时间录制导致 sample table 缓存溢出。

八、miniMP4 MSI 的 AAC/Opus 分流

文件:sdk/lib/video/miniMP4/mp4_encode_msi2.c

mp4_has_audio 从只接受 AAC_ENC 改为同时接受 AAC_ENCOPUS_ENC。批处理函数根据编码器设置 audio_head_size

编码器 头部长度 写入 MP4 的数据
AAC 7 字节 data + 7,长度减 7
Opus 0 字节 data 全部内容

每个 framebuff 的 AUDIO_INFO.time_interval 填入 durations[]。批量条件满足时调用对应的 batch 接口;条件不满足时退回逐帧写入,但仍按编码器选择正确函数:

1
2
AAC  → write_aac_data_batch / write_aac_data
Opus → write_opus_data_batch / write_opus_data

录制启动时,MSI 将编码器映射为 MP4_AUDIO_CODEC_NONEMP4_AUDIO_CODEC_AACMP4_AUDIO_CODEC_OPUS。设置音频采样率后,只有 AAC 调用 get_aac_configmp4_audio_cfg_init,Opus 不再使用 AAC 的 AudioSpecificConfig。

九、录制 UI 的配置和资源生命周期

文件:sdk/app/ui/mp4_recorder_ui.c

新增默认配置:

1
2
3
#ifndef MP4_RECORD_AUDIO_CODEC
#define MP4_RECORD_AUDIO_CODEC AAC_ENC
#endif

默认行为仍为 AAC;改为 OPUS_ENC 后,UI 创建 Opus 编码器并让 MP4 MSI 使用 Opus 音频轨。

状态成员由 aac_msi 改为通用的 audio_msi。启动流程是:创建编码器、增加 MP4 输出、传入音频类型初始化 MP4 MSI。任一步骤失败时,统一删除音频输出、反初始化编码器并释放 H.264/MP4 MSI。停止录制和退出页面复用同一清理路径,避免 Opus 模式遗留编码器资源。

十、端到端调用链

1
2
3
4
5
6
7
8
9
10
11
ADC 采样

audio_encode_init(AAC_ENC / OPUS_ENC)
↓ AUDIO_INFO { nsamples, samplerate, time_interval }
mp4_encode_audio_batch_write()
├─ AAC:去除 7 字节 ADTS → write_aac_data(_batch)
└─ Opus:保留完整帧 → write_opus_data(_batch)

mdat + stts/stsz/stco

mp4_sync

十一、改动后的三种录制模式

模式 编码器 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 视频录制流程不受影响。