更新记录
1.2.2(2026-08-31)
修复 iOS 云打包链接失败——用 iOS 的用户请升级后重新制作自定义基座(Android 端不受影响)。
- 修复:iOS 云打包(Appstore/真机/模拟器渠道)在 1.2.1 上编译可通过、但链接阶段失败:
Undefined symbols for architecture arm64: "_ffi_uni_rust_opus_rustbuffer_free" ...(一批_ffi_uni_rust_opus_*/_uniffi_uni_rust_opus_*符号未定义,Ld ... unimoduleNexOpusBUILD FAILED)。 根因:云打包工程对utssdk/app-ios/Frameworks/里的静态库只加搜索路径、 不加入链接输入,插件核心静态库没有被链进插件模块。 现随包在utssdk/app-ios/Libs/附带双架构(真机 arm64 + 模拟器 x86_64)静态库, 由打包工程直接加入链接阶段,两条分发目录并存、互不冲突。 - 无 API 变化,业务代码零改动。升级后务必删掉旧版插件重新导入,并重新制作一次 自定义基座 / 重新云打包(改动在原生层,热刷新不生效)。
1.2.1(2026-08-28)
修复 iOS 打包失败——用到 iOS 的用户务必升级(Android 端不受影响,此前版本 Android 一切正常)。
- 修复:iOS 云打包 / 制作自定义基座时,本插件编译失败:
cannot find type 'RustBuffer' in scope(连带ForeignBytes/RustCallStatus以及一批ffi_uni_rust_opus_*、uniffi_uni_rust_opus_checksum_*符号找不到),最终EmitSwiftModule normal arm64 (in target 'unimoduleNexOpus')BUILD FAILED。 根因:插件的底层 FFI 头文件此前只随utssdk/app-ios/Frameworks/*.xcframework分发, 而打包工程只把该目录加进 framework/library 搜索路径、不加头文件搜索路径, 于是部分 Xcode 版本下 Swift 侧解析不到该模块、声明整片缺失。 现随包附带utssdk/app-ios/Libs/,头文件改由插件 target 的伞头文件内联引入。 - 改进:iOS 模拟器切片补齐 x86_64(此前仅 arm64),不再出现
Undefined symbols for architecture x86_64。 - 无 API 变化,业务代码零改动;删掉旧版重新导入即可。
1.2.0(2026-08-25)
新增 opus 裸流(无 ogg 容器)↔ WAV 转码与文件形态探测。
- 新增
opusDecodeRawToWav(inPath, outPath, options):把没有 ogg 容器封装的.opus裸流 解成 WAV。嵌入式对讲机 / 硬件采集板 / 自定义传输协议存下来的.opus多是这种形态,普通播放器 和标准解码流程都打不开。裸流不含采样率/声道信息,按设备规格在options里给出即可; 传进来的若其实是标准 ogg 容器会自动改走容器路径,不会报错。 - 新增
opusEncodeWavToRaw(inPath, outPath, bitrate, options):WAV → opus 裸流, 默认framing:'fixed'(自动开硬 CBR 保证每包等长,嵌入式最通用的形态)。 - 新增
opusProbeFile(path):探测一个.opus到底是容器还是裸流、哪种分帧、每包多少字节、 几声道、每包多少毫秒——照着结果填解码参数最稳妥。 - 分帧约定:裸 opus 包流不自定界(包内不含自身长度),支持四种业界常见约定
fixed(等长 CBR)/len16be/len16le(2 字节长度前缀)/len8(1 字节长度前缀), 以及auto自动嗅探。嗅不出就明确报错,绝不猜着解(猜错只会产出一段噪音音频)。 - 修复:插件类型声明
interface.uts改为自包含,买家工程里传对象/数组字面量入参不再可能 遇到「实际类型为 UTSJSONObject / 预期类型为 XXX」这类 error17(与 nex-modbus 1.2.0 同源修复)。 - 改进:
opusDecodeFileToWav遇到非 ogg 输入时改报可操作的提示(指路裸流 API), 不再抛晦涩的 ogg 解析错误。
平台兼容性
uni-app(5.14)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| × | √ | × | × | √ | - | 5.0 | 14 | × |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | × | × | × | × | × | × | × |
uni-app x(5.14)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| × | × | 5.0 | 14 | × | × |
nex-opus 音频编解码(opus 格式)
uni-app x / 经典 uni-app(Vue3)双框架的 opus 音频编解码插件:packet 级流式编解码(对讲 / 实时语音传输)+ 文件级 WAV↔opus 转码(录音存储 / 传输省流量)。高性能原生实现,全程离线,无网络依赖。
数据形态(先看这条,最常见的误会):流式 API 收发的 PCM 样本与 opus packet 都是
number[](packet 为字节值 0..255),不是Uint8Array/ArrayBuffer;文件级 API 全部 路径进 / 路径出——inPath/outPath/wavPath这类参数传的是文件路径字符串,不是音频数据本身。路径怎么传:Android 端支持绝对路径、
file://URI 及 uni 虚拟路径(uni.env.CACHE_PATH的unifile://、_doc/等,插件内部自动转换);iOS 端请传绝对路径或file://URI。
为什么选 opus
- 体积:16kbps 规格下 1 秒语音仅约 2KB(原始 PCM 32KB,压缩 16:1);一分钟录音 ≈120KB。
- 实时:20ms 一帧、恒定码率可选,帧长可预期(16kbps CBR 每帧稳定 40 字节),适合对讲、 连麦、IM 语音消息、IoT 语音透传等按帧走网络的场景。
- 通用:opus 是 WebRTC / 主流 IM 通用的开放音频格式,产物
.opus(ogg 容器)主流播放器可直接播。
支持端
| 端 | 支持 | 说明 |
|---|---|---|
| App-Android | ✅ | arm64-v8a / armeabi-v7a / x86_64 |
| App-iOS | ✅ | iOS 14+ |
| H5 / 小程序 | ❌ | 音频编解码为 App 端原生能力;platformCapabilities() 可探测 |
框架:uni-app x(uvue)与经典 uni-app(Vue3)双框架均支持,同一套 API、同一份原生实现;
经典 uni-app 里用 <script setup> 直接 import ... from '@/uni_modules/nex-opus' 即可(示例见
examples/uni-app/pages/opus/opus.vue)。两个框架都只在 App 端(Android/iOS)生效,需用自定义基座 / 云打包运行。
快速开始
流式编解码 · uni-app x(对讲/实时语音)
import { createOpusEncoder, createOpusDecoder } from '@/uni_modules/nex-opus'
// 16kHz 单声道 / 16kbps / 20ms 帧 / 恒定码率(对讲推荐规格)
const enc = createOpusEncoder({
sampleRate: 16000, channels: 1, bitrate: 16000,
cbr: true, frameMs: 20, application: 'voip'
})
const dec = createOpusDecoder({ sampleRate: 16000, channels: 1 })
const frameLen = enc!.frameSamples() // 320:每帧样本数,录音回调按此切帧
// pcm: number[],16bit 有符号样本(-32768..32767),长度 = frameLen × channels
const packet = enc!.encodeFrame(pcm) // 一帧 → 一个 opus packet(number[] 字节)
const pcmOut = dec!.decodePacket(packet) // 一个 packet → 一帧 PCM
enc!.destroy(); dec!.destroy() // 用完显式释放
流式编解码 · 经典 uni-app(Vue3)
经典 uni-app 页面在 WebView,返回的编解码器对象过原生桥会被序列化、方法丢失——故经典端用 handle 句柄 + 自由函数(序列化安全)。推荐自建薄包装类(方法定义在页面 JS、不过桥),用法即与 uni-app x 一致:
import {
opusEncoderCreate, opusEncoderEncode, opusEncoderFrameSamples, opusEncoderDestroy,
opusDecoderCreate, opusDecoderDecode, opusDecoderDestroy,
} from '@/uni_modules/nex-opus'
// 薄包装类:方法在页面 JS 定义,经典端可正常调用
class OpusEncoder {
constructor(opts) { this.h = opusEncoderCreate(opts) }
frameSamples() { return opusEncoderFrameSamples(this.h) }
encodeFrame(pcm) { return opusEncoderEncode(this.h, pcm) }
destroy() { opusEncoderDestroy(this.h) }
}
class OpusDecoder {
constructor(opts) { this.h = opusDecoderCreate(opts) }
decodePacket(pkt) { return opusDecoderDecode(this.h, pkt) }
destroy() { opusDecoderDestroy(this.h) }
}
const enc = new OpusEncoder({ sampleRate: 16000, channels: 1, bitrate: 16000, cbr: true, frameMs: 20, application: 'voip' })
const dec = new OpusDecoder({ sampleRate: 16000, channels: 1 })
const frameLen = enc.frameSamples() // 320
const packet = enc.encodeFrame(pcm) // 一帧 PCM16 → opus packet
const pcmOut = dec.decodePacket(packet) // packet → 一帧 PCM
enc.destroy(); dec.destroy()
也可不建包装类、直接用自由函数:
const h = opusEncoderCreate(opts); opusEncoderEncode(h, pcm); opusEncoderDestroy(h)。 文件转码 / 版本 / 能力探测两个框架写法完全一致(本就是自由函数)。完整经典 demo 见examples/uni-app/pages/opus/opus.vue。
文件转码(录音压缩存储)
import { opusEncodeWavFile, opusDecodeFileToWav } from '@/uni_modules/nex-opus'
// WAV → .opus(ogg/opus 容器,主流播放器可播)
const stats = await opusEncodeWavFile(srcWavPath, outOpusPath, 24000)
console.log(`压缩后 ${stats.bytesOut} 字节 / ${stats.frames} 帧 / ${stats.durationMs}ms`)
// .opus → WAV(样本数精确还原)
await opusDecodeFileToWav(opusPath, outWavPath)
opus 裸流(无 ogg 容器)转码
.opus 有两种形态:标准 ogg/opus 容器(有文件头,播放器能直接播),以及裸流——把编码器
吐出的一个个 opus packet 直接首尾相连写盘、没有任何文件头。嵌入式对讲机 / 硬件采集板 /
自定义传输协议存下来的 .opus 多是后者,普通播放器和标准解码流程都打不开。
裸 opus 包流不自定界(包内不含自身长度),必须靠一个「分帧约定」才能切包。本插件支持:
| framing | 约定 | 典型来源 |
|---|---|---|
fixed |
每包等长(硬 CBR 下每帧字节数恒定,如 16kbps/20ms = 40B) | 嵌入式 CBR 采集 |
len16be / len16le |
每包前置 2 字节长度(大端 / 小端) | 自定义 TCP/串口协议 |
len8 |
每包前置 1 字节长度(包 ≤255B) | 低码率对讲协议 |
auto |
先嗅 ogg 头;否则逐个试解,全流消费完 + 逐包结构校验 + 试解码通过才采信 | 默认 |
下面三个 API 也是「路径进 / 路径出」:
rawStreamPath/wavPath传的是裸流文件的路径, 不是裸流的二进制数据(Uint8Array/ArrayBuffer不能直传)。内存里已经拿到整段裸流的话, 先uni.getFileSystemManager().writeFile()写成文件再传路径(这样才能用opusProbeFile探测 +framing自动嗅探);若是网络 / 串口 / 蓝牙一包一包实时来的,请改用上面的流式 API (opusDecoderCreate+opusDecoderDecode,packet 同样是number[]字节数组),不必落盘。
import { opusProbeFile, opusDecodeRawToWav, opusEncodeWavToRaw } from '@/uni_modules/nex-opus';
// ① 不确定自己的文件是什么形态?先探一下
const info = await opusProbeFile(rawStreamPath);
// { container:'raw', framing:'fixed', frameBytes:40, packets:50, channels:1,
// sampleRate:0, frameMs:20, durationMs:1000, bytes:2000 }
// 注意 sampleRate 恒为 0——裸流里没有采样率信息,要按设备规格自己填
// ② 裸流 → WAV(采样率/声道必填;framing 不填就自动嗅探)
const ds = await opusDecodeRawToWav(rawStreamPath, wavPath, { sampleRate: 16000, channels: 1 });
// 设备规格已知时也可以写死,最确定:
// await opusDecodeRawToWav(rawStreamPath, wavPath,
// { sampleRate: 16000, channels: 1, framing: 'fixed', frameBytes: 40 });
// ③ WAV → 裸流(默认等长 CBR)
const es = await opusEncodeWavToRaw(wavPath, rawStreamPath, 16000, { framing: 'fixed', frameMs: 20 });
嗅不出就报错,绝不猜着解:分帧约定不匹配时会 reject(
container error/invalid param) 并提示改用opusProbeFile或显式指定参数——猜错分帧只会解出一段噪音音频,那比报错更糟。 另:opusDecodeFileToWav(容器版)遇到裸流输入会明确提示改用opusDecodeRawToWav。
API 一览
| API | 形态 | 说明 |
|---|---|---|
createOpusEncoder(options) |
同步 | 建流式编码器;参数越界拒绝不静默修正 |
createOpusDecoder(options) |
同步 | 建流式解码器 |
OpusEncoder.encodeFrame(pcm) |
同步 | 一帧 PCM → 一个 packet(µs~ms 级,适合录音回调内联调用) |
OpusDecoder.decodePacket(packet) |
同步 | 一个 packet → 一帧 PCM;坏包安全拒绝不闪退 |
resetState() / destroy() |
同步 | 流中断重同步 / 显式释放 |
frameSamples() / sampleRate() / channelCount() |
同步 | 创建参数快照 |
opusEncodeWavFile(in, out, bitrate) |
Promise | WAV → .opus(后台线程,不冻 UI) |
opusDecodeFileToWav(in, out) |
Promise | .opus → WAV |
opusVersion() / platformCapabilities() |
同步 | 版本 / 端能力探测 |
参数合法值(越界报错,不静默修正):sampleRate ∈ 8000/12000/16000/24000/48000;
channels 1/2;bitrate [6000, 510000];frameMs ∈ 2.5/5/10/20/40/60;
application voip/audio/lowdelay。
裸流 API
opusDecodeRawToWav(inPath, outPath, { sampleRate, channels, framing?, frameBytes? })→Promise<OpusDecodeStats>opusEncodeWavToRaw(inPath, outPath, bitrate, { framing?, frameMs? })→Promise<OpusEncodeStats>opusProbeFile(path)→Promise<OpusProbeInfo>
错误行为(双端差异)
- Android:参数越界 / 坏包 / 已 destroy 抛异常(message 含详情),
try/catch捕获。 - iOS:同场景哨兵返回(
create*返回null;encodeFrame/decodePacket返回空数组), 详情见控制台日志;文件级 Promise 双端一致 reject。
注意事项
- WAV 仅支持 16bit PCM、采样率须在合法值列表内(本插件不做重采样,越界明确报错)。
- 录音采集本身请用 uni 录音 API / 原生录音插件;本插件专注编解码(PCM 进出)。
- packet 是「一帧一包」:网络传输自行组帧(长度前缀 / WebSocket 二进制帧均可)。

收藏人数:
购买普通授权版(
试用
使用 HBuilderX 导入示例项目
赞赏(0)
下载 1182
赞赏 0
下载 12564047
赞赏 1949
赞赏
京公网安备:11010802035340号