Skip to content

Commit b5bb7b7

Browse files
committed
升级:优化代码和修复部分bug
- 优化Recorder open重试错误信息,修复onProcess提供了async函数时没有录音数据的问题 - BufferStreamPlayer新增clearInput方法,允许中断老的音频数据播放来播放新的 - uniapp适配文件修复vue3 Fragments(multi-root 多个根节点)的兼容性问题;修复uniapp Android自带的XXPermissions库在后台无法请求权限的问题(仅限搭配原生录音插件可用) - 小程序适配文件MiniProgramWx_WriteLocalFile接口bug修复
1 parent 60f7bfc commit b5bb7b7

38 files changed

+666
-136
lines changed

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,11 +340,19 @@ var blob=new Blob([arrayBuffer],{type:"audio/mp3"});
340340
var file=new File([arrayBuffer],"xxx.mp3");
341341
var arrayBuffer=reader.result; //用FileReader的readAsArrayBuffer方法读取 Blob/File 成ArrayBuffer
342342

343-
//二进制拼接合并,下面Int16Array换成Uint8Array等其他TypedArray也是一样的
343+
//pcm二进制拼接合并,下面Int16Array换成Uint8Array等其他TypedArray也是一样的
344344
var pcm1=new Int16Array([1,2]),pcm2=new Int16Array([3,4,5,6]);
345345
var pcm=new Int16Array(pcm1.length+pcm2.length);
346346
pcm.set(pcm1,0); //写入pcm1到开头位置
347347
pcm.set(pcm2,pcm1.length); //写入pcm2到pcm1后面,完成拼接
348+
349+
//二进制封装包头,比如要发送特定封包格式的数据包
350+
var bytes=new Uint8Array(pcm.buffer); //需要封装的音频二进制数据
351+
var header=new Uint8Array(2); //假设2字节包头
352+
header[0]=0xEF; header[1]=0x01; //填充任意需要的内容
353+
var packet=new Uint8Array(header.length+bytes.length); //拼接
354+
packet.set(header,0);
355+
packet.set(bytes,header.length); //拼接完成
348356
```
349357

350358

@@ -1060,6 +1068,8 @@ stream.start(()=>{
10601068

10611069
//随时都能调用input,会等到start成功后播放出来,不停的调用input,就能持续的播放出声音了,需要暂停播放就不要调用input就行了
10621070
stream.input(anyData);
1071+
//清除已输入但还未播放的数据,一般用于非实时模式打断老的播放;返回清除的音频时长,默认会从总时长duration中减去此时长,keepDuration=true时不减去
1072+
stream.clearInput(keepDuration);
10631073

10641074
//暂停播放,暂停后:实时模式下会丢弃所有input输入的数据(resume时只播放新input的数据),非实时模式下所有input输入的数据会保留到resume时继续播放
10651075
stream.pause();
@@ -1404,12 +1414,12 @@ EncodeMix对象:
14041414
所有音频格式的编码器都在`/src/engine`目录中(或`/dist/engine`目录的压缩版),每个格式一般有一个同名的js文件,如果这个格式有额外的编码引擎文件(`*-engine.js`)的话,使用时必须要一起加上。
14051415

14061416
## pcm 格式
1407-
依赖文件:`pcm.js`,支持实时编码(边录边转码),pcm编码器输出的数据其实就是Recorder中的buffers原始数据(经过了重新采样),16位时为LE小端模式(Little Endian),并未经过任何编码处理;pcm为未封装的原始音频数据,pcm数据文件无法直接播放,pcm加上一个44字节wav头即成wav文件,可通过wav格式来正常播放。两个参数相同的pcm文件直接二进制拼接在一起即可成为长的pcm文件[pcm片段文件合并+可移植源码:PCMMerge](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.encode_transfer_frame_pcm)
1417+
依赖文件:`pcm.js`,支持实时编码(边录边转码),pcm编码器输出的数据其实就是Recorder中的buffers原始数据(经过了重新采样),16位时为LE小端模式(Little Endian),并未经过任何编码处理;pcm为未封装的原始音频数据,pcm数据文件无法直接播放,pcm加上一个44字节wav头即成wav文件,可通过wav格式来正常播放。两个参数相同的pcm文件直接二进制拼接在一起即可成为长的pcm文件。
14081418

14091419
### Recorder.pcm2wav(data,True,False)
14101420
已实现的一个把pcm转成wav格式来播放的方法,`data = { sampleRate:16000 pcm的采样率 , bitRate:16 pcm的位数 取值:8 或 16 , blob:pcm的blob对象 }``True=fn(wavBlob,duration)`。要使用此方法需要带上`wav`格式编码器。
14111421

1412-
提示:16位的pcm二进制数据可以直接通过`new Int16Array(pcmArrayBuffer)`来构造成Int16Array,然后通过`rec.mock`来转码成其他任意格式。
1422+
提示:pcm二进制数据可以直接在数据开头添加个wav头(`Recorder.wav_header`)即成wav文件;16位的pcm可直接通过`new Int16Array(pcmArrayBuffer)`来构造成Int16Array,然后通过`rec.mock`来转码成其他任意格式。
14131423

14141424

14151425
## wav (raw pcm format) 格式
@@ -1418,6 +1428,11 @@ EncodeMix对象:
14181428
### wav转pcm
14191429
生成的wav文件内音频数据的编码为未压缩的pcm数据(raw pcm),只是在pcm数据前面加了一个44字节的wav头;因此直接去掉前面44字节就能得到原始的pcm数据,如:`blob.slice(44,blob.size,"audio/pcm")`;注意:其他wav编码器可能不是44字节的头,要从任意wav文件中提取pcm数据,请参考:`assets/runtime-codes/fragment.decode.wav.js`
14201430

1431+
### Recorder.wav_header(format,numCh,sampleRate,bitRate,dataLength)
1432+
生成一个wav头(Uint8Array),添加到pcm二进制数据开头即成wav文件。比如实时保存pcm时(追加写入文件),可在文件开头预留44字节的位置,等录音结束后再来生成wav头并写入到文件开头。使用例子[PCM基础教程](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.pcm.basic)
1433+
1434+
`format: 1 (raw pcm) 2 (ADPCM) 3 (IEEE Float) 6 (g711a) 7 (g711u),比如pcm传1``numCh: 声道数,比如1``sampleRate: 音频数据采样率,比如16000``bitRate: 音频数据位数,比如16``dataLength: wav中的音频数据二进制长度,比如pcm.byteLength`
1435+
14211436
### 简单将多段小的wav片段合成长的wav文件
14221437
由于RAW格式的wav内直接就是pcm数据,因此将小的wav片段文件去掉wav头后得到的原始pcm数据合并到一起,再加上新的wav头即可合并出长的wav文件;要求待合成的所有wav片段的采样率和位数需一致。[wav合并参考和测试+可移植源码](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.merge.wav_merge)
14231438

app-support-sample/demo_UniApp/README.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,16 @@
4444
## 一、引入js文件
4545
1. 在你的项目根目录安装`recorder-core``npm install recorder-core --registry=https://registry.npmmirror.com/`
4646
2. 导入Recorder-UniCore组件:直接复制本目录下的`uni_modules/Recorder-UniCore`组件到你项目中,或者到[DCloud 插件市场下载此组件](https://ext.dcloud.net.cn/plugin?name=Recorder-UniCore)
47-
3. 在需要录音的vue文件内编写以下代码,按需引入需要的js
47+
3. 项目配置好录音权限,参考下面的录音权限配置章节,**特别注意App后台录音配置、小程序权限声明**
48+
4. 在需要录音的vue文件script内编写以下代码,按需引入需要的js
4849

4950
``` html
51+
<template>
52+
<view>
53+
... 建议template下只有一个根节点(最外面套一层view),如果不小心踩到了vue3的Fragments(multi-root 多个根节点)特性(vue2编译会报错,vue3不会),可能会出现奇奇怪怪的兼容性问题
54+
</view>
55+
</template>
56+
5057
<script> /**这里是逻辑层**/
5158
//必须引入的Recorder核心(文件路径是 /src/recorder-core.js 下同),使用import、require都行
5259
import Recorder from 'recorder-core' //注意如果未引用Recorder变量,可能编译时会被优化删除(如vue3 tree-shaking),请改成 import 'recorder-core',或随便调用一下 Recorder.a=1 保证强引用
@@ -78,11 +85,14 @@ import '@/uni_modules/Recorder-UniCore/app-uni-support.js'
7885
</script>
7986
```
8087

88+
5. 编译成app时,默认需要额外提供一个renderjs模块,请照抄下面这段代码放到vue文件末尾
8189
``` html
8290
<!-- #ifdef APP -->
8391
<script module="yourModuleName" lang="renderjs"> //此模块内部只能用选项式API风格,vue2、vue3均可用,请照抄这段代码;不可改成setup组合式API风格,否则可能不能import vue导致编译失败
8492
/**需要编译成App时,你需要添加一个renderjs模块,然后一模一样的import上面那些js(微信的js除外)
85-
,因为App中默认是在renderjs(WebView)中进行录音和音频编码**/
93+
,因为App中默认是在renderjs(WebView)中进行录音和音频编码
94+
。如果配置了 RecordApp.UniWithoutAppRenderjs=true 且未调用依赖renderjs的功能时(如nvue、可视化、仅H5中可用的插件)
95+
,可不提供此renderjs模块,同时逻辑层中需要将相关import的条件编译去掉**/
8696
import 'recorder-core'
8797
import RecordApp from 'recorder-core/src/app-support/app'
8898
import '../../uni_modules/Recorder-UniCore/app-uni-support.js' //renderjs中似乎不支持"@/"打头的路径,如果编译路径错误请改正路径即可
@@ -121,7 +131,7 @@ export default {
121131

122132
//RecordApp.UniNativeUtsPlugin={ nativePlugin:true }; //App中启用配套的原生录音插件支持,配置后会使用原生插件进行录音,没有原生插件时依旧使用renderjs H5录音
123133
//App中提升后台录音的稳定性:配置了原生插件后,可配置 `RecordApp.UniWithoutAppRenderjs=true` 禁用renderjs层音频编码(WebWorker加速),变成逻辑层中直接编码(但会降低逻辑层性能),后台运行时可避免部分手机WebView运行受限的影响
124-
//App中提升后台录音的稳定性:需要启用后台录音保活服务(iOS不需要),Android 9开始,锁屏或进入后台一段时间后App可能会被禁止访问麦克风导致录音静音、无法录音(renderjs中H5录音也受影响),请调用配套原生插件的`androidNotifyService`接口,或使用第三方保活插件
134+
//App中提升后台录音的稳定性:需要启用后台录音保活服务(iOS不需要,参考录音权限配置),Android 9开始,锁屏或进入后台一段时间后App可能会被禁止访问麦克风导致录音静音、无法录音(renderjs中H5录音也受影响),请调用配套原生插件的`androidNotifyService`接口,或使用第三方保活插件
125135

126136
export default {
127137
data() { return {} } //视图没有引用到的变量无需放data里,直接this.xxx使用
@@ -299,7 +309,7 @@ data() { return {} } //视图没有引用到的变量无需放data里,直接th
299309

300310
[](?)
301311

302-
# 部分原理和需要注意的细节
312+
# 录音权限配置、需要注意的细节
303313
## 编译成H5时录音和权限
304314
编译成H5时,录音功能由Recorder H5提供,无需额外处理录音权限。
305315

@@ -311,6 +321,8 @@ data() { return {} } //视图没有引用到的变量无需放data里,直接th
311321

312322
小程序录音需要用户授予录音权限,调用`RecordApp.RequestPermission`的时候会检查是否能正常录音,如果用户拒绝了录音权限,会进入错误回调,回调里面你应当编写代码检查`wx.getSetting`中的`scope.record`录音权限,然后引导用户进行授权(可调用`wx.openSetting`打开设置页面,方便用户给权限)。
313323

324+
**注意:上架小程序需要到小程序管理后台《[用户隐私保护指引](https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/miniprogram-intro.html)》中声明录音权限,否则正式版将无法调用录音功能(请求权限时会直接走错误回调)。**
325+
314326
更多细节请参考 [miniProgram-wx](../miniProgram-wx) 测试项目文档。
315327

316328

@@ -653,6 +665,8 @@ writeFile 数据写入文件,可新建文件、追加写入(文件流写入
653665
返回:{
654666
fullPath:"/文件绝对路径"
655667
}
668+
> 参考demo项目中 pages/recTest/test_native_plugin.vue 中的 realtimeWritePcm2Wav 方法
669+
> ,有用到实时写入pcm数据,并在结束时在文件开头写入wav头,即可生成wav文件
656670

657671
readFile 读取文件,可流式读取
658672
参数:{

app-support-sample/demo_UniApp/pages/recTest/main_recTest.vue

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ DCloud 插件市场下载组件: https://ext.dcloud.net.cn/plugin?name=Recorder-
44
-->
55

66
<template>
7-
<view>
7+
<view> <!-- 建议template下只有一个根节点(最外面套一层view),如果不小心踩到了vue3的Fragments(multi-root 多个根节点)特性(vue2编译会报错,vue3不会),可能会出现奇奇怪怪的兼容性问题 -->
88
<view style="padding:5px 10px 0">
99
<text style="vertical-align: top;"></text>
1010

@@ -176,6 +176,9 @@ DCloud 插件市场下载组件: https://ext.dcloud.net.cn/plugin?name=Recorder-
176176
<button size="mini" type="default" @click="speakerOffClick">切换成听筒播放</button>
177177
</view>
178178
<!-- #ifdef APP -->
179+
<view>
180+
<button size="mini" type="default" @click="testWritePcm2Wav">pcm数据实时写入到wav文件测试(原生插件)</button>
181+
</view>
179182
<view>
180183
<button size="mini" type="default" @click="testNativePlugin">测试原生插件调用</button>
181184
<button size="mini" type="default" @click="testShowMemoryUsage">显示内存占用</button>
@@ -239,7 +242,7 @@ DCloud 插件市场下载组件: https://ext.dcloud.net.cn/plugin?name=Recorder-
239242
</template>
240243

241244

242-
<script>
245+
<script> /**这里是逻辑层**/
243246
/**本例子是VUE的选项式 API (Options API)风格,vue2 vue3均支持,如果你使用的vue3 setup组合式 API (Composition API) 编写时,请在import后面取到当前实例this,在需要this的地方传vue3This变量即可,具体调用可以参考 page_vue3____composition_api.vue
244247
import { getCurrentInstance } from 'vue';
245248
var vue3This=getCurrentInstance().proxy; //必须定义到最外面,放import后面即可 */
@@ -826,6 +829,10 @@ export default {
826829
,testRenderjsFunc(){
827830
this.$refs.testRF.showTest();
828831
}
832+
//pcm数据实时写入到wav文件测试(原生插件)
833+
,testWritePcm2Wav(){
834+
this.$refs.testNP.realtimeWritePcm2Wav();
835+
}
829836
//测试原生插件功能
830837
,testNativePlugin(){
831838
this.$refs.testNP.showTest();
@@ -868,6 +875,18 @@ export default {
868875
return this.reclog("目前仅App或小程序支持支持切换外放和听筒播放",1);
869876
}
870877
878+
//显示测试用的H5播放器
879+
,showH5Player(url,play){
880+
var jsCode=`
881+
var el=document.querySelector(".testH5Play5FView");
882+
el.innerHTML='<div style="padding-top:8px"><audio class="testH5Play5FAudio" controls style="width:100%"></audio></div>';
883+
var audio=document.querySelector(".testH5Play5FAudio");
884+
audio.src=${JSON.stringify(url||"")};
885+
if(${!!play}) audio.play();
886+
`;
887+
/*#ifdef H5*/ eval(jsCode); return; /*#endif*/
888+
/*#ifdef APP*/ RecordApp.UniWebViewEval(this,jsCode); return; /*#endif*/
889+
}
871890
//H5播放5分钟wav
872891
,testH5Play5F(){
873892
var jsCode=`(function(log){
@@ -948,7 +967,12 @@ export default {
948967

949968

950969
<!-- #ifdef APP -->
951-
<script module="testMainVue" lang="renderjs"> //此模块内部只能用选项式API风格,vue2、vue3均可用;不可改成setup组合式API风格,否则可能不能import vue导致编译失败
970+
<script module="testMainVue" lang="renderjs"> //此模块内部只能用选项式API风格,vue2、vue3均可用,请照抄这段代码;不可改成setup组合式API风格,否则可能不能import vue导致编译失败
971+
/**需要编译成App时,你需要添加一个renderjs模块,然后一模一样的import上面那些js(微信的js除外)
972+
,因为App中默认是在renderjs(WebView)中进行录音和音频编码
973+
。如果配置了 RecordApp.UniWithoutAppRenderjs=true 且未调用依赖renderjs的功能时(如nvue、可视化、仅H5中可用的插件)
974+
,可不提供此renderjs模块,同时逻辑层中需要将相关import的条件编译去掉**/
975+
952976
/**============= App中在renderjs中引入RecordApp,这样App中也能使用H5录音、音频可视化 =============**/
953977
/** 先引入Recorder **/
954978
import Recorder from 'recorder-core'; //注意如果未引用Recorder变量,可能编译时会被优化删除(如vue3 tree-shaking),请改成 import 'recorder-core',或随便调用一下 Recorder.a=1 保证强引用

0 commit comments

Comments
 (0)