更新记录
1.0.0(2026-07-24)
自用,不保证稳定,谨慎购买
平台兼容性
uni-app(5.15)
| Vue2 | Vue2插件版本 | Vue3 | Vue3插件版本 | Chrome | Safari | app-vue | app-vue插件版本 | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | 1.0.0 | √ | 1.0.0 | √ | √ | √ | 1.0.0 | - | 12.0 | × | × |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | × | × | × | × | × | × | × |
uni-app x(5.15)
| Chrome | Safari | Android | Android插件版本 | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|---|
| √ | √ | 12.0 | 1.0.0 | × | × | × |
tki-audio-recorder
音频录制插件,支持实时 PCM 帧数据回调、PCM 流播放、通话模式(AEC + NS + AGC)。
适用于:H5(AudioWorklet)、Android(AudioRecord)、iOS(AVAudioEngine,不确定)。
目录
安装
通过 uni_modules 安装(推荐)
在 HBuilderX 中右键项目目录 → 插件市场 → 搜索 tki-audio-recorder → 点击安装。
手动安装
将 uni_modules/tki-audio-recorder 目录复制到项目的 uni_modules/ 目录下。
配置
Android: 插件会自动处理录音权限。Android 6.0+ 需要运行时权限,插件内部已处理。
iOS: 项目 manifest.json → App模块配置 → 勾选 AVFoundation(音频录制和播放)。
如果需要配置隐私权限描述,可在 manifest.json 的 iOS 节点下添加:
{
"plist": {
"NSMicrophoneUsageDescription": "需要麦克风权限以录制音频"
}
}
快速开始
import {
// 录音控制
recordStart, recordStop, recordPause, recordResume, recordMute, recordUnmute,
// 录音事件
onRecordStop, onRecordError, onRecordTimer, onRecordFrame, onRecordDecibel, onRecordWaveform,
// PCM 播放
pcmInit, pcmPlay, pcmPlayWav, pcmAppend, pcmStop, pcmReplay, pcmClear, pcmStatus,
// 播放事件
onPcmStatus, onPcmError, onPcmDecibel, onPcmWaveform, onPcmTimer,
// 资源销毁
recordDestroy, pcmDestroy,
} from '@/uni_modules/tki-audio-recorder'
// 1. 开始录音
recordStart({
sampleRate: 16000,
enableCommunicationMode: true,
format: 'wav',
})
// 2. 监听录音结束
onRecordStop((info) => {
console.log('录音结束,时长:', info.duration, '秒')
console.log('WAV 文件:', info.tempFilePath)
// 回放:通过 PCM 播放器(含 FFT 频谱/分贝/计时)
if (info.tempFilePath) {
pcmPlayWav(info.tempFilePath)
// 或用系统播放器: uni.createInnerAudioContext()
}
})
// 3. 停止录音
setTimeout(() => {
recordStop()
}, 5000) // 5 秒后停止
API 文档
录音控制
| 函数 | 说明 | 参数 |
|---|---|---|
recordStart(options?) |
开始录音 | RecordOptions |
recordStop() |
停止录音 | — |
recordPause() |
暂停录音(麦克风仍占用,计时暂停) | — |
recordResume() |
恢复录音(计时从中断处继续) | — |
recordMute() |
静音:帧回调推送零数据,分贝/波形回调停止 | — |
recordUnmute() |
取消静音,恢复所有回调 | — |
recordStart(options?)
recordStart({
sampleRate: 16000, // 采样率,默认 16000
numberOfChannels: 1, // 声道数,默认 1(单声道)
format: 'wav', // 录音格式:'wav' 或 'pcm'
duration: 0, // 录音时长限制(ms),0=不限时
bufferSize: 4096, // onRecordFrame 回调触发阈值
enableCommunicationMode: true, // 通话模式(含 AEC+NS+AGC)
})
format='wav':录音结束后生成 WAV 文件并通过onRecordStop返回文件路径format='pcm':仅通过onRecordFrame回调原始 PCM 数据,不生成文件- 如果已处于录音状态,重复调用会被忽略
录音事件监听
| 函数 | 回调参数 | 触发时机 | 静音时 |
|---|---|---|---|
onRecordStop(cb) |
RecordEndInfo |
录音停止/中断 | — |
onRecordError(cb) |
{ errSubject, errCode, errMsg } |
录音出错(UniError 对象) | — |
onRecordFrame(cb) |
string(base64) |
帧缓冲达到 bufferSize |
✅ 推送零数据 |
onRecordDecibel(cb) |
number(dBFS) |
约 16ms 间隔 | ❌ 不触发 |
onRecordWaveform(cb) |
number[](64点) |
约 16ms 间隔 | ❌ 不触发 |
onRecordTimer(cb) |
number(秒) |
每秒一次 | ✅ 继续触发 |
错误码说明:
| 错误码 | 说明 |
|---|---|
| 9010001 | 录音初始化失败 |
| 9010002 | 录音启动失败(权限被拒/麦克风被占用) |
| 9010003 | 录音停止失败 |
| 9010004 | 录音被系统中断(如来电) |
| 9010005 | 播放器初始化失败 |
| 9010006 | 播放数据无效 |
onRecordFrame(callback)
回调固定返回 base64 编码的字符串(16-bit 小端序 Int16 PCM),三端一致。
每帧样本数等于 bufferSize(默认 4096,约 256ms@16kHz)。
如需标准 ArrayBuffer,使用 uni.base64ToArrayBuffer() 转换:
onRecordFrame((base64) => {
const pcm = uni.base64ToArrayBuffer(base64) // ArrayBuffer
const samples = new Int16Array(pcm) // Int16 PCM 样本
console.log('收到帧,样本数:', samples.length)
})
onRecordDecibel(callback)
onRecordDecibel((db: number) => {
// db: 范围约 -96 ~ 0(-96 ≈ 静音,0 ≈ 最大声)
})
onRecordWaveform(callback)
64 段 FFT 频谱归一化值 [0, 1],对应 0~8000Hz(@16kHz 采样率)。
onRecordWaveform((arr: number[]) => {
const bass = arr.slice(0, 2).reduce((a, b) => a + b, 0) / 2 // ~0~250Hz
const mid = arr.slice(2, 32).reduce((a, b) => a + b, 0) / 30 // ~250~4000Hz
const treble = arr.slice(32).reduce((a, b) => a + b, 0) / 32 // ~4000~8000Hz
})
录音状态查询
| 函数 | 返回 | 说明 |
|---|---|---|
isRecording() |
boolean |
是否正在录音 |
isRecordPaused() |
boolean |
是否暂停中 |
isRecordMuted() |
boolean |
是否静音 |
PCM 播放控制
| 函数 | 说明 |
|---|---|
pcmInit(config?) |
初始化 PCM 播放器。重复调用自动销毁旧实例 |
pcmPlay(data) |
播放 PCM 数据(base64 字符串 / 字符串数组) |
pcmPlayWav(filePath) |
播放 WAV 文件(自动解析 → PCM 播放,支持实时频谱) |
pcmAppend(data, immediatePlay?) |
追加 base64 数据到播放队列 |
pcmPause() |
暂停播放 |
pcmResume() |
恢复播放 |
pcmStop() |
停止播放,清空队列 |
pcmReplay() |
从历史存档重新播放 |
pcmClear() |
清空队列和历史数据 |
pcmStatus() |
获取播放器状态 → PlaybackStatus |
pcmPlay data 支持的类型:
| 类型 | 说明 |
|---|---|
string |
base64 编码的 PCM 数据 |
string[] |
多个 base64 字符串批量播放 |
pcmPlay只接受 base64 PCM 数据,不支持 ArrayBuffer 或 WAV 文件路径。如需播放 WAV 文件,请使用pcmPlayWav或uni.createInnerAudioContext()。
pcmInit({ inputSampleRate: 16000 }) // 页面加载时初始化一次
// 使用 onRecordFrame 配合 pcmPlay(推荐方式)
onRecordFrame((base64) => {
pcmPlay(base64) // 实时回放
queue.push(base64) // 存入队列
})
// 批量播放
pcmPlay([base64Frame1, base64Frame2])
// 播放 WAV 文件(自动解析 → PCM 播放,FFT/分贝/计时均有效)
pcmPlayWav('file:///storage/emulated/0/.../recording.wav')
pcmPlayWav('blob:http://localhost:5173/xxxxx')
// 追加到队列
pcmAppend(base64Frame)
pcmAppend(base64Frame, true) // 追加并立即播放
pcmInit(config?) 配置项 PlayerConfig:
PlayerConfig 参数表:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
inputSampleRate |
number |
16000 |
输入 PCM 数据的采样率(Hz) |
numChannels |
number |
1 |
声道数:1=单声道,2=立体声 |
bitDepth |
number |
16 |
位深度:16 或 32 |
littleEndian |
boolean |
true |
是否小端字节序 |
pcmType |
'int' \| 'float' |
'int' |
PCM 数据类型 |
bufferSize |
number |
4096 |
帧缓冲区大小(采样点数),应与 recordStart 的 bufferSize 一致,用于计算播放完成超时 |
bufferSize应与录音时的recordStart({ bufferSize })保持一致。例如录音配bufferSize: 1024,则pcmInit({ bufferSize: 1024 })。Android 端使用此值动态计算 AudioTrack 缓冲区大小和无数据超时,防止流式回放时误判播放结束。
pcmStatus() 返回值 PlaybackStatus:
| 字段 | 类型 | 说明 |
|---|---|---|
isPlaying |
boolean |
是否正在播放 |
isPaused |
boolean |
是否暂停 |
isEnded |
boolean |
是否播放结束(队列自然耗尽) |
isStopped |
boolean |
是否被主动停止 |
queueLength |
number |
队列中待播数据块数 |
contextState |
string |
上下文状态:running | suspended | closed |
播放事件监听
| 函数 | 回调参数 | 说明 |
|---|---|---|
onPcmStatus(cb) |
PlaybackStatus |
播放器状态变化(播放/暂停/结束/停止) |
onPcmError(cb) |
{ errSubject, errCode, errMsg } |
播放出错(UniError 对象) |
onPcmDecibel(cb) |
number(dBFS) |
实时分贝值(≈60fps),与 onRecordDecibel 算法一致 |
onPcmWaveform(cb) |
number[](64点) |
实时 FFT 频谱(≈60fps),与 onRecordWaveform 算法一致 |
onPcmTimer(cb) |
number(秒) |
播放时长(每秒触发) |
资源销毁
| 函数 | 说明 |
|---|---|
recordDestroy() |
销毁录音资源(不触发任何回调)。推荐在 onUnload 中调用 |
pcmDestroy() |
销毁 PCM 播放器。推荐在 onUnload 中调用 |
onUnload(() => {
recordDestroy() // 先销毁录音
pcmDestroy() // 再销毁播放器
})
recordDestroy()和pcmDestroy()已分离,可根据业务场景单独调用。recordDestroy()不会触发任何回调(先清回调再调 stop,防止最后一帧触发 pcmPlay 等副作用)。
RecordOptions 配置项
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
sampleRate |
number |
16000 |
采样率(Hz),常用:8000 / 16000 / 44100 |
numberOfChannels |
number |
1 |
声道数:1=单声道,2=立体声 |
format |
wav pcm |
wav |
wav=生成 WAV 文件;pcm=仅帧回调 |
duration |
number |
0 |
录音时长限制(ms),0=不限时,>=1000 生效 |
bufferSize |
number |
4096 |
onRecordFrame 触发阈值(采样点数),默认约 256ms@16kHz |
enableCommunicationMode |
boolean |
true |
true=开启 AEC+NS+AGC;false=全部关闭(原始录音) |
enableCommunicationMode 各平台实现
| 平台 | true |
false |
|---|---|---|
| H5 | getUserMedia 约束全部开启 |
getUserMedia 约束全部关闭 |
| Android | AudioSource.VOICE_COMMUNICATION |
AudioSource.UNPROCESSED(API 26+)或 MIC |
| iOS | AVAudioSessionModeVoiceChat |
AVAudioSessionModeDefault |
Android 回声消除说明
Android 端启用 enableCommunicationMode: true 只是选择了 VOICE_COMMUNICATION 音频源,硬件 AEC 要真正生效,还需要满足以下条件:
- 使用
pcmPlay播放音频(不能只用uni.createInnerAudioContext) - 在
recordStart()之前调用pcmInit(),确保 PCMPlayer 已创建 - 在录音进行中播放(停止录音后 sessionId 失效)
// ✅ AEC 生效的正确顺序
pcmInit({ inputSampleRate: 16000 }) // 1. 先创建 PCMPlayer
recordStart({ // 2. AudioRecord 启动,sessionId=123
enableCommunicationMode: true, // 自动传到 PCMPlayer
})
// 录音中...
pcmPlay(data) // 3. AudioTrack 用 sessionId=123 创建 ✅ 硬件 AEC
// ❌ AEC 无效的典型错误
recordStart({}) // 1. 录音启动,但 player==null,sessionId 无法传递
pcmInit({}) // 2. 新建 player,sessionId=0
pcmPlay(data) // 3. AudioTrack 以 sessionId=0 创建 ❌ 无 AEC
原理:Android 硬件 AEC 要求
AudioRecord和AudioTrack使用同一个 audioSessionId。recordStart获取真实 sessionId,传给PCMPlayer,pcmPlay第一次创建AudioTrack时使用该 sessionId,系统此时才能挂载硬件 AEC。
RecordEndInfo 回调信息
onRecordStop 回调的参数:
| 字段 | 类型 | 说明 |
|---|---|---|
tempFilePath |
string |
WAV 文件路径(format='wav' 时有效),可直接用于 uni.createInnerAudioContext |
duration |
number |
录音时长(秒),已扣除暂停时间 |
fileSize |
number |
文件大小(字节) |
interrupted |
boolean |
是否被外部中断(如来电) |
各平台文件路径格式
| 平台 | 示例路径 |
|---|---|
| H5 | blob:http://localhost:5173/xxxx-xxxx |
| Android | file:///storage/emulated/0/Android/data/.../recording_2026_07_15_14_30_00_1234.wav |
| iOS | /private/var/mobile/Containers/.../tmp/recording_2026_07_15_14_30_00_1234.wav |
完整示例
录音 + 文件回放
import {
recordStart, recordStop, recordPause, recordResume,
recordMute, recordUnmute,
onRecordStop, onRecordError, onRecordTimer,
recordDestroy, pcmDestroy,
} from '@/uni_modules/tki-audio-recorder'
const state = reactive({
isRec: false,
isPaused: false,
isMuted: false,
duration: 0,
filePath: '',
})
// 录音控制
function startRec() {
state.filePath = ''
state.duration = 0
recordStart({
sampleRate: 16000,
enableCommunicationMode: true,
format: 'wav',
bufferSize: 4096,
})
state.isRec = true
}
function stopRec() { recordStop() }
function pauseRec() { recordPause(); state.isPaused = true }
function resumeRec() { recordResume(); state.isPaused = false }
function muteRec() { recordMute(); state.isMuted = true }
function unmuteRec() { recordUnmute(); state.isMuted = false }
// 事件监听
onRecordStop((info) => {
state.isRec = false
state.isPaused = false
state.filePath = info.tempFilePath
console.log(`录制 ${info.duration}秒, 文件: ${info.tempFilePath}`)
})
onRecordTimer((sec) => { state.duration = sec })
// 回放
function playWav() {
if (!state.filePath) return
const audio = uni.createInnerAudioContext()
audio.src = state.filePath
audio.onEnded(() => audio.destroy())
audio.onError(() => audio.destroy())
audio.play()
}
// 页面卸载
onUnload(() => {
recordDestroy()
pcmDestroy()
})
实时 PCM 回放(对讲机模式)
import {
recordStart, recordStop, pcmInit, pcmPlay,
onRecordFrame, onRecordStop, onRecordError,
recordDestroy, pcmDestroy,
} from '@/uni_modules/tki-audio-recorder'
// 页面加载时初始化播放器
pcmInit({ inputSampleRate: 16000 })
// 监听录音错误(如权限被拒)
onRecordError((err) => {
console.error('录音出错:', err.errCode, err.errMsg)
uni.showToast({ title: '录音失败', icon: 'none' })
})
// 录音时实时回放(base64 直传,无需转换)
onRecordFrame((data) => {
pcmPlay(data)
})
function start() {
recordStart({
sampleRate: 16000,
enableCommunicationMode: true,
format: 'pcm',
bufferSize: 4096,
})
}
onUnload(() => {
recordDestroy()
pcmDestroy()
})
format: 'pcm'避免生成无用的 WAV 文件,减少开销。
平台差异说明
功能差异
| 功能 | H5 | Android | iOS |
|---|---|---|---|
| 录音引擎 | AudioWorklet | AudioRecord | AVAudioEngine |
| 播放引擎 | AudioContext + BufferSource | AudioTrack | AVAudioPlayerNode |
| WAV 文件 | ✅ Blob URL | ✅ 文件路径 | 不确定(文件路径) |
| 帧回调(bufferSize) | ✅ 每 4096 样本 | ✅ 每 4096 样本 | 不确定(每 4096 样本) |
| 分贝/波形 | ✅ | ✅ | 不确定 |
| pause/resume | ✅ | ✅ | 不确定 |
| mute/unmute | ✅(写入零数据) | ✅(写入零数据) | 不确定(写入零数据) |
| AEC (enableCommunicationMode) | getUserMedia 约束 |
VOICE_COMMUNICATION |
不确定(.voiceChat 模式) |
| onRecordDecibel | 频域能量 | 时域 RMS | 不确定(时域 RMS) |
| onRecordWaveform | 频域 FFT 128 点 → 64 段 | FFT 128 点 → 64 段 | 不确定(FFT 128 点 → 64 段) |
onRecordFrame 返回数据格式
三端统一返回 base64 编码的字符串(16-bit 小端序 Int16 PCM),无平台差异。
onRecordFrame((base64) => {
// 三端一致,无需任何平台判断
const pcm = uni.base64ToArrayBuffer(base64) // 可选:转 ArrayBuffer
const samples = new Int16Array(pcm)
})
常见问题
Q: 录音后无法播放 WAV 文件?
Android 端 WAV 文件保存在 externalCacheDir(/sdcard/Android/data/.../cache/),uni.createInnerAudioContext 可以直接访问。
如果使用文件管理器找不到文件,请确认路径包含 Android/data/(外部存储)。
Q: 为什么静音后 WAV 文件还是原来的大小?
静音不改变文件时长和大小——只是 PCM 数据替换为零。这样保证 tempFilePath 指向的 WAV 文件时长总是正确的。
Q: pcmPlay 只接受 base64,为什么?
Android 端 UTS 桥接对 ArrayBuffer/Uint8Array 等二进制类型存在类型丢失问题,base64 字符串可完全规避。
如需从 ArrayBuffer 转换为 base64,使用 btoa 或 uni.arrayBufferToBase64。
Q: H5 端录制时间长后页面卡顿?
H5 端的 PCM 数据采用分块存储(pcmChunks: Float32Array[]),避免每帧拷贝整个缓冲区。即使是长时间录音内存占用也有限。
Q: 录音时长不准?
Web 端使用 AudioContext.currentTime(暂停时自动冻结)。
Android/iOS 使用 elapsedPaused 累计暂停时间扣除。
如果发现时长偏差,请确认是否误调了 pause()/resume()。
Q: 录音文件保存在哪里?
| 平台 | 目录 | 清理时机 |
|---|---|---|
| H5 | 内存 Blob | 页面刷新后失效 |
| Android | externalCacheDir |
App 缓存清理时 |
| iOS | NSTemporaryDirectory |
系统定期清理 |
如需持久保存,请将文件复制到应用的其他目录。
Q: 支持后台录音吗?
- H5: 不支持(浏览器限制,页面不可见时 getUserMedia 会暂停)
- Android: 支持(Service 保活需额外配置)
- iOS: 不确定(需配置 Background Modes → Audio, AirPlay, and Picture in Picture)

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 32982
赞赏 11
下载 12454432
赞赏 1935
赞赏
京公网安备:11010802035340号