更新记录

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 ... unimoduleNexOpus BUILD 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_PATHunifile://_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* 返回 nullencodeFrame/decodePacket 返回空数组), 详情见控制台日志;文件级 Promise 双端一致 reject。

注意事项

  • WAV 仅支持 16bit PCM、采样率须在合法值列表内(本插件不做重采样,越界明确报错)。
  • 录音采集本身请用 uni 录音 API / 原生录音插件;本插件专注编解码(PCM 进出)。
  • packet 是「一帧一包」:网络传输自行组帧(长度前缀 / WebSocket 二进制帧均可)。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据。所有音频编解码均在本地完成,不联网、不上传任何音频或文件。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。