3.B:vendor opencore-amr + AMRCodec Swift wrapper,打通 AMR↔WAV 编解码
为支撑 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 诊断。
This commit is contained in:
Vendored
+109
@@ -0,0 +1,109 @@
|
||||
# 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_VERSION` load
|
||||
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 形式记录即可。
|
||||
|
||||
## 替换 / 升级流程(未来用)
|
||||
|
||||
1. 从 SourceForge / GitHub mirror 拉新版本源码(如 0.1.6)
|
||||
2. 用 `xcrun -sdk iphoneos clang` + `-arch arm64` 跨编 device slice
|
||||
3. 用 `xcrun -sdk iphonesimulator clang` + `-arch arm64`/`-arch x86_64` 跨编
|
||||
simulator slice
|
||||
4. `lipo -create -segalign 8` 合并为新 fat `.a`
|
||||
5. 替换 `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 中显式声明。
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/* ------------------------------------------------------------------
|
||||
* Copyright (C) 2009 Martin Storsjo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
* -------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef OPENCORE_AMRNB_INTERF_DEC_H
|
||||
#define OPENCORE_AMRNB_INTERF_DEC_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void* Decoder_Interface_init(void);
|
||||
void Decoder_Interface_exit(void* state);
|
||||
void Decoder_Interface_Decode(void* state, const unsigned char* in, short* out, int bfi);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/* ------------------------------------------------------------------
|
||||
* Copyright (C) 2009 Martin Storsjo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
* -------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef OPENCORE_AMRNB_INTERF_ENC_H
|
||||
#define OPENCORE_AMRNB_INTERF_ENC_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef AMRNB_WRAPPER_INTERNAL
|
||||
/* Copied from enc/src/gsmamr_enc.h */
|
||||
enum Mode {
|
||||
MR475 = 0,/* 4.75 kbps */
|
||||
MR515, /* 5.15 kbps */
|
||||
MR59, /* 5.90 kbps */
|
||||
MR67, /* 6.70 kbps */
|
||||
MR74, /* 7.40 kbps */
|
||||
MR795, /* 7.95 kbps */
|
||||
MR102, /* 10.2 kbps */
|
||||
MR122, /* 12.2 kbps */
|
||||
MRDTX, /* DTX */
|
||||
N_MODES /* Not Used */
|
||||
};
|
||||
#endif
|
||||
|
||||
void* Encoder_Interface_init(int dtx);
|
||||
void Encoder_Interface_exit(void* state);
|
||||
int Encoder_Interface_Encode(void* state, enum Mode mode, const short* speech, unsigned char* out, int forceSpeech);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user