// // AMRCodec.swift // ylgamehall // // AMR-NB ↔ WAV 文件级编解码的 Swift 薄 wrapper。 // // ## 为何如此 // // AMR-NB 是 H5 语音功能的跨端契约硬约束(iOS / Android / 旧版本客户端都按 // AMR-NB 解码 audiourl),格式不能换。详见 Vendor/OpenCoreAMR/README.md。 // // 实际编解码由 daoqi 沿用的 `amrFileCodec.mm`(C++ 文件级操作 — 解析 WAV // header、按 PCM 帧调 opencore-amr,写 AMR 文件头/帧)执行,本文件只是 Swift // 侧的薄包装。 // // - 不在 Swift 层重写 amrFileCodec:那几百行 C 风格 byte 流操作(FILE* / // memcpy / 字节序解析)是 Swift 最别扭的领域,重写收益小风险大;daoqi 版 // 本已上线 N 年稳定运行 // - amrFileCodec.{h,mm} 放在 Source/Audio/AMRFileCodec/,由 synchronized // group 自动编译;amrFileCodec.h 通过 ylgamehall-Bridging-Header.h 暴露 // 给 Swift // // ## 用途 // // - 录音链路(prepareaudio):`wavToAmr(_:to:)` 把 AVAudioRecorder 录的 wav // 转 amr 后上传七牛 // - 播放链路(mediaTypeAudio):`amrToWav(_:to:)` 把七牛下载的 amr 转 wav 后 // 交 AVAudioPlayer 播 // // 契约:docs/H5-Native-Contract.md §3.1 [4][5] + §3.2 [9] // import Foundation public enum AMRCodec { public enum Error: Swift.Error, Sendable { /// EncodeWAVEFileToAMRFile 返回 0(fopen 失败或写 0 帧) case wavToAmrFailed(wav: String, amr: String) /// DecodeAMRFileToWAVEFile 返回 0(amr 文件不存在 / 头校验失败 / wav 创建失败) case amrToWavFailed(amr: String, wav: String) } /// WAV → AMR。 /// /// 录音的 wav 必须满足 8kHz / 16bit / mono(VoiceRecorderBaseVC.m 的 /// `getAudioRecorderSettingDict` 输出,msext 多年约定);否则 /// amrFileCodec 的 ReadPCMFrame 拿不到完整 160 sample 帧,返回 0。 /// /// - Throws: `AMRCodec.Error.wavToAmrFailed` 当底层 C 函数返回 0 public static func wavToAmr(_ wav: URL, to amr: URL) throws { let frames = EncodeWAVEFileToAMRFile(wav.path, amr.path, 1, 16) guard frames > 0 else { throw Error.wavToAmrFailed(wav: wav.path, amr: amr.path) } } /// AMR → WAV。 /// /// 解码后的 wav 是 8kHz / 16bit / mono,可直接交 AVAudioPlayer 播放。 /// /// - Throws: `AMRCodec.Error.amrToWavFailed` 当底层 C 函数返回 0 public static func amrToWav(_ amr: URL, to wav: URL) throws { let frames = DecodeAMRFileToWAVEFile(amr.path, wav.path) guard frames > 0 else { throw Error.amrToWavFailed(amr: amr.path, wav: wav.path) } } }