更新记录

1.0.7(2026-08-13)

更新日志 外部录音设备时强制不开启回声消除,开了也没用

1.0.6(2026-08-11)

修复云打包错误

1.0.5(2026-08-11)

更新文档

查看更多

平台兼容性

uni-app(5.15)

Vue2 Vue3 Chrome Safari app-vue app-vue插件版本 app-nvue Android iOS 鸿蒙
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 帧数据回调、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.jsonApp模块配置 → 勾选 AVFoundation(音频录制和播放)。

如果需要配置隐私权限描述,可在 manifest.jsoniOS 节点下添加:

{
  "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() 停止录音。内部:停止录音引擎 → 吐出剩余的帧数据回调 → 触发 onRecordStop
recordPause() 暂停录音(麦克风仍占用,计时暂停)
recordResume() 恢复录音(计时从中断处继续)
recordMute() 静音:帧回调推送零数据,分贝/波形回调停止
recordUnmute() 取消静音,恢复所有回调

内部调用链

函数 内部调用 触发回调?
recordStop() 吐出剩余帧 → 停止录音引擎 ✅ 触发 onRecordStop
recordDestroy() 先清回调 → recorder.stop() ❌ 不触发(回调已清完,防止最后一帧反调触发 pcmPlay

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)。

标准 ArrayBufferuni.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() 从历史存档重新播放:内部调 stop() + 从 allData 重建队列 + play()
pcmClear() 清空队列和历史数据。三端一致:内部调 stop() → 清 playQueue → 清 allData
pcmStatus() 获取播放器状态 → PlaybackStatus

内部调用链(仅记录有副作用的函数,纯查询/设置类省略):

函数 内部调用 playQueue allData 还能 pcmReplay()?
pcmStop() player.stop() ❌ 清空 ✅ 保留 ✅ 能
pcmClear() player.stop() + 清 allData ❌ 清空 ❌ 清空 ❌ 不能
pcmDestroy() player.destroy()player.stop() + 释放资源 + 置空回调 → player=null ❌ 不可访问 ❌ 不可访问 ❌ 不能
pcmReplay() player.stop() + 从 allData 重建队列 + play() 重建 ✅ 保留

不要冗余

  • pcmStop() + pcmClear() → 只需 pcmClear()
  • pcmStop() + pcmDestroy() → 只需 pcmDestroy()

pcmPlay data 支持的类型

类型 说明
string base64 编码的 PCM 数据
string[] 多个 base64 字符串批量播放

pcmPlay 只接受 base64 PCM 数据,不支持 ArrayBuffer 或 WAV 文件路径。播放 WAV 文件用 pcmPlayWavuni.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 帧缓冲区大小(采样点数),应与 recordStartbufferSize 一致,用于计算播放完成超时

bufferSize 需与 recordStart({ bufferSize }) 一致。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 中调用

内部调用链:

函数 内部调用 触发回调?
recordDestroy() 先清回调 → recorder.stop() ❌ 不触发
pcmDestroy() player.destroy()player.stop() + 释放资源 + 置空回调 → player=null ❌ 不触发
onUnload(() => {
  recordDestroy()   // 已含 stop,不触发回调
  pcmDestroy()      // 已含 stop,销毁播放器
})

不要冗余

  • pcmStop() + pcmClear() → 只需 pcmClear()
  • pcmStop() + pcmDestroy() → 只需 pcmDestroy()
  • recordStop() + recordDestroy() → 只需 recordDestroy()(需要 onRecordStop 时除外)

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=全部关闭(原始录音)。⚠️ Android 端当 enableExternalMic=true 时该参数强制失效,始终按 false 处理
enableExternalMic boolean false Android 专用true=优先路由到 USB 音频输入设备(如 USB 摄像头麦克风),并强制禁用系统回声消除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 生效需满足以下条件

  1. 使用 pcmPlay 播放音频(不能只用 uni.createInnerAudioContext
  2. recordStart() 之前调用 pcmInit(),确保 PCMPlayer 已创建
  3. 在录音进行中播放(停止录音后 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 要求 AudioRecordAudioTrack同一个 audioSessionIdrecordStart 获取真实 sessionId 传给 PCMPlayerpcmPlay 首次创建 AudioTrack 时用该 sessionId,系统才挂载硬件 AEC。

Android USB 录音设备说明

Android 端可通过 enableExternalMic: true 将录音输入路由到外置 USB 音频设备(如 USB 摄像头麦克风、USB 声卡),适合需要外部拾音的场景。

recordStart({
  enableExternalMic: true,          // 优先使用 USB 麦克风
  // 注意:enableExternalMic=true 时 enableCommunicationMode 被强制忽略,
  // 无论传 true/false,系统回声消除(AEC)一律禁用
  enableCommunicationMode: false,
})

前提条件

  1. 系统要求:Android 8.0(API 26)及以上
  2. 设备要求:系统能识别到 USB 音频输入设备(TYPE_USB_DEVICETYPE_USB_HEADSET
  3. 未检测到 USB 设备时静默跳过,自动使用系统默认输入设备,不影响正常录音

注意事项

项目 说明
AEC 回声消除 强制禁用enableExternalMic=true 时系统回声消除一律关闭(即使 enableCommunicationMode=true 也被忽略)——外置 USB 麦克风与内置扬声器分属不同硬件路径,系统 HAL 无法做跨设备回声抵消,强行开启反而引入回声/劣化问题
ROM 兼容性 部分 ROM 的 setPreferredDevice() 返回 false(不支持路由切换),此时静默降级到系统默认输入设备
采样率 USB 麦克风通常支持 16kHz~48kHz,建议保持默认 16000 / 单声道
平台 Android 生效,H5 / iOS 传入该参数被忽略

原理enableExternalMic: true 时,AudioRecord 创建后调用 setPreferredDevice() 显式指定 USB 设备作为输入源;无 USB 设备或路由失败时静默降级,不改变默认行为。


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(enableExternalMic=true 时强制禁用) 不确定(.voiceChat 模式)
USB 外置麦克风 (enableExternalMic) ✅(API 26+,无设备时静默降级,强制禁用 AEC)
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,使用 btoauni.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)

隐私、权限声明

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

麦克风权限

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

插件会访问麦克风进行录音,PCM音频数据仅在本地处理,不会上传

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