为支撑 H5 的 prepareaudio / mediaTypeAudio 录音上传 + 远端语音播放
(docs/H5-Native-Contract.md §3.1 [4][5] + §3.2 [9])接入 AMR-NB
编解码能力。AMR-NB 是跨端(iOS / Android / 旧版本客户端)契约硬约束,
不可换 AAC / Opus。
## 接入路径
直接 vendor daoqi msext 现成的 fat .a(已上线多年),不打 xcframework /
不重编源码:
- Vendor/OpenCoreAMR/v0.1.x/libopencore-amrnb.a:daoqi 同款 5-slice fat
库(含 2026-06-20 lipo -segalign 8 修过的对齐版本)
- Vendor/OpenCoreAMR/v0.1.x/{interf_dec,interf_enc}.h:amrnb 公开头
- Vendor/OpenCoreAMR/README.md:来源、版本、修复历史、为何不打 xcframework
- ylgamehall/Source/Audio/AMRFileCodec/{amrFileCodec.h,amrFileCodec.mm}:
daoqi 同款 wrapper(文件级 WAV↔AMR 处理)
- ylgamehall/Source/Audio/AMRCodec.swift:Swift 薄 wrapper(wavToAmr /
amrToWav 两个纯函数 + AMRCodec.Error)
理由见 Vendor/OpenCoreAMR/README.md「为何不打 xcframework / 不重编源码」
一节,本质是 opencore-amr 整个 iOS 生态没有"现代主流"分发形态,社区无
官方 SPM / xcframework 维护,强行打反而引入新风险。
## 关键技术点:extern "C" 修复 ObjC++ name mangling
amrFileCodec.h 在原项目里没有 extern "C" 包裹函数声明,daoqi 能跑是
因为调用方 VoiceConverter.mm 也是 ObjC++,双方按相同 C++ name mangling
链接能对上。
新外壳从 Swift 通过 bridging-header 调用,Swift 按 C 函数解析,期望符号
_EncodeWAVEFileToAMRFile / _DecodeWAVEFileToWAVEFile(C 链接);但
.mm 编译时默认 C++ name mangling,符号变成 __Z25Encode...,导致链接器
报 Undefined symbol。
修复:在 amrFileCodec.h 的函数声明外加 #ifdef __cplusplus / extern "C"
包裹。这是标准 C/C++ 头文件兼容写法,对 daoqi 老路径无影响(ObjC++
调用方仍能链接 C-linkage 函数)。
## .gitignore:白名单 Vendor 下的 .a
发现历史遗留问题:原 .gitignore *.a 通配规则把 libWeChatSDK.a 也排除
了,意味着别人 clone 此仓库后链接失败。本次顺带修复:
!Vendor/**/*.a
让 Vendor 下的 .a 显式纳入跟踪。本提交同时把 libWeChatSDK.a(历史
缺失)+ libopencore-amrnb.a(本次新增)一并加入 git 跟踪。
## 工程改动
pbxproj 改动由用户在 Xcode UI 完成(4 项 Build Settings:Add .a 到
Frameworks group / Header Search Paths +2 / Library Search Paths +1;
EXCLUDED_ARCHS arm64-simulator 因真机调试场景暂未配,需要时再加)。
## 验证
BuildProject 通过,0 错误,符号链接正确。XcodeRefreshCodeIssuesInFile
对 AMRCodec.swift 0 诊断。
OpenCoreAMR
opencore-amr 是 AMR-NB / AMR-WB 语音编解码的 C 库(OpenCORE 项目,Martin
Storsjo 维护)。新外壳沿用 daoqi msext 时期的同一份二进制 + 公开头,不打
xcframework 不重新编译。理由见下文「ADR」一节。
用途
支撑 H5 的语音功能(prepareaudio 录音 → AMR 编码 → 七牛上传;
mediaTypeAudio 下载 → AMR 解码 → 播放)。AMR-NB 格式是跨端(iOS / Android /
旧版本客户端)契约硬约束,不可换成 AAC / Opus。详见
docs/H5-Native-Contract.md §3.1 [4][5] + §3.2 [9]。
来源
- 上游:https://sourceforge.net/projects/opencore-amr/
- 当前镜像:https://github.com/BelledonneCommunications/opencore-amr
- 二进制版本:daoqi 沿用,约 2014 年打包,对应上游 0.1.3 时间点(v0.1.x 目
录命名取自此)。具体小版本无可考——
.a二进制无版本号字段,public 头 (interf_enc.h/interf_dec.h) 自 0.1.0 以来 ABI 未变 - daoqi 项目路径:
daoqi/msext/Class/Common/VoiceConvert/{lib/, opencore-amrnb/}
目录
v0.1.x/
├── libopencore-amrnb.a (5 slice fat:i386 + x86_64 + armv7 + armv7s + arm64-device)
├── interf_dec.h (AMR-NB 解码 C API)
└── interf_enc.h (AMR-NB 编码 C API)
不 vendor libopencore-amrwb.a——daoqi amrFileCodec.mm 实际只用 NB(窄带,
8kHz 单声道),WB 路径源码注释掉了,新外壳同样只用 NB。
fat header 对齐补丁
daoqi .a 在 2026-06-20 用 lipo -segalign 8 重新打包过,所有 5 个 slice 的
fat header align = 2^3 (8)。这是为了适配 Xcode 26+ 的新链接器
ld_prime 对 fat 库 architecture slice 对齐字段的硬性要求(≥ 2^3);不修则
会要 -ld_classic 过渡选项(已确认是定时炸弹)。
详细背景与修复命令见 youle_app_ios/CLAUDE.md 「2026-06-20:opencore-amr 静
态库 fat header 重新对齐」一节。
已知遗留链接器警告(无害):
No platform load command found in ... assuming: iOS(每个 .o 一行,约 154 行)— 2014 年 .o 不带LC_VERSION_MIN_IPHONEOS/LC_BUILD_VERSIONload command;新链接器假定为 iOS,行为正确Reducing alignment of section __DATA,__common from 0x8000 to 0x4000
要彻底消除这两类警告需要重编 opencore-amr 源码,违反「稳定 > 一切」原 则——接受现状。
simulator-arm64 缺失
.a 是 2014 年打包,没有 arm64-simulator slice(这是 2020 年 Apple
Silicon 上市后才有的概念)。M1+ Mac 上 simulator 编译需要在 Build Settings 设
置:
EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64
让 simulator 退回 x86_64 切片(通过 Rosetta 跑)。仅影响开发期 simulator, release device 是 arm64 完整可用。
为何不打 xcframework / 不重编源码?
ADR:选 vendor 现成 .a 而非现代化 xcframework / 源码重编。
- 依据:
opencore-amr整个 iOS 生态都是"老 vendor 模式"——各家大厂(微 信、QQ、网易云信、声网 IM 等)都内部 vendor.a,没人公开维护 SPM / 官方 xcframework。社区无"现代主流"形态可参照 - 稳定性:daoqi 这份
.a已上线 N 年稳定运行;现代化"形态"(xcframework) 本质上仍是 vendor 同一份二进制字节,形态收益边际,引入新打包/编译路径反而 增加风险 - 跨编辑成本:要完整 xcframework 必须从源码编出 arm64-simulator slice,跨 编 iOS(autoconf)容易踩坑,1+ 小时调试 vs vendor 几分钟落地
- CLAUDE.md 原则一致:项目「稳定 > 一切」+「daoqi 是工作参照实现」精神 要求避免"为现代化而现代化"。本目录的现代化收益体现在组织规范 + 文档完 备(与 WechatSDK Vendor 并列、README 来源/版本/修复历史齐全),而不是打包 形态
未来若 Xcode 大版本彻底干掉对 fat .a 的支持(被动升级),再走 xcframework
化或源码重编路线,按 CLAUDE.md「Xcode 26.5+ 兼容性 Build Settings」一节的
patch 形式记录即可。
替换 / 升级流程(未来用)
- 从 SourceForge / GitHub mirror 拉新版本源码(如 0.1.6)
- 用
xcrun -sdk iphoneos clang+-arch arm64跨编 device slice - 用
xcrun -sdk iphonesimulator clang+-arch arm64/-arch x86_64跨编 simulator slice lipo -create -segalign 8合并为新 fat.a- 替换
v0.1.x/下文件,更新本 README、提交
链接配置
- Build Phases → Link Binary With Libraries:加
libopencore-amrnb.a - Build Settings → Header Search Paths:追加
$(PROJECT_DIR)/Vendor/OpenCoreAMR/v0.1.x - Build Settings → Library Search Paths:追加
$(PROJECT_DIR)/Vendor/OpenCoreAMR/v0.1.x - Build Settings → Excluded Architectures(iphonesimulator):
arm64
wrapper 源码(amrFileCodec.{h,mm} + AMRCodec.swift)放在
ylgamehall/Source/Audio/AMRFileCodec/ 与 ylgamehall/Source/Audio/,
由项目同步组(PBXFileSystemSynchronizedRootGroup)自动加入编译,无需在
pbxproj 中显式声明。