Skip to content

Commit

Permalink
更新demo和文档
Browse files Browse the repository at this point in the history
- 在线测试页面添加实时语音通话聊天对讲测试功能
- 小程序demo添加录音上传、实时语音通话聊天对讲测试功能
- App原生项目更新,gitee.io换成github.io,Android重新打包apk,iOS弃用AVAudioRecorder改用AudioUnit
  • Loading branch information
xiangyuecn committed Jul 12, 2024
1 parent 8806e2d commit ac2e440
Show file tree
Hide file tree
Showing 35 changed files with 1,921 additions and 240 deletions.
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,35 @@ $.ajax({
```


[](?)

## 【附】js中的二进制基础知识:Uint8Array Int16Array ArrayBuffer Blob/File
更多js二进制数据基础知识请阅读:[js二进制转换-Base64/Hex/Int16Array/ArrayBuffer/Blob](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.js-binary-bytes)
``` javascript
//XMLHttpRequest、WebSocket等一般都能直接send(TypedArray/ArrayBuffer/Blob)到服务器,服务器会收到二进制数据
//TypedArray/ArrayBuffer在绝大部分js运行环境中都支持,Blob只在浏览器中支持

//Recorder的buffers是pcm数组,单个pcm是Int16Array(TypedArray子类)
var arrayBuffer=pcm.buffer; //pcm的Int16Array转ArrayBuffer
var pcm=new Int16Array(arrayBuffer); //pcm的ArrayBuffer转成Int16Array(共享内存,arrayBuffer.slice(0)截取新的不共享)

//Recorder的takeoffEncodeChunk得到的chunkBytes是Uint8Array(TypedArray子类,类似于无符号byte[])
var arrayBuffer=chunkBytes.buffer; //chunkBytes的Uint8Array转ArrayBuffer
var chunkBytes=new Uint8Array(arrayBuffer); //任意ArrayBuffer均能转成Uint8Array

//ArrayBuffer 与 Blob/File 可以相互转换,File是Blob的一个特例(子类)
var blob=new Blob([arrayBuffer],{type:"audio/mp3"});
var file=new File([arrayBuffer],"xxx.mp3");
var arrayBuffer=reader.result; //用FileReader的readAsArrayBuffer方法读取 Blob/File 成ArrayBuffer

//二进制拼接合并,下面Int16Array换成Uint8Array等其他TypedArray也是一样的
var pcm1=new Int16Array([1,2]),pcm2=new Int16Array([3,4,5,6]);
var pcm=new Int16Array(pcm1.length+pcm2.length);
pcm.set(pcm1,0); //写入pcm1到开头位置
pcm.set(pcm2,pcm1.length); //写入pcm2到pcm1后面,完成拼接
```





Expand Down Expand Up @@ -1516,15 +1545,23 @@ $T.G("key-xx2",["v1"], "ru"); //明确返回俄语,不管当前lang设置的

# :open_book:其它功能介绍

## 语音通话聊天demo:实时编码、传输与播放验证
[线测试Demo](https://xiangyuecn.github.io/Recorder/)中包含了一个语音通话聊天的测试功能,没有服务器支持所以仅支持局域网内一对一语音。用两个设备(浏览器打开两个标签也可以)打开demo,勾选H5版语音通话聊天,按提示交换两个设备的信息即可成功进行P2P连接,然后进行语音。实际使用时数据传输可以用WebSocket,会简单好多
## 测试用的本地服务器
[/assets/node-localServer](./assets/node-localServer) 目录内提供了一个nodejs服务,实现了http上传接口、websocket接口。多个demo中有使用到,方便测试

编写本语音测试的目的在于验证H5录音实时转码、传输的可行性,并验证实时转码mp3格式小片段文件接收后的可播放性。经测试发现:除了移动端可能存在设备性能低下的问题以外,录音后实时转码mp3并传输给对方是可行的,对方接收后播放也能连贯的播放(效果还是要看播放代码写的怎么样,目前没有比较完美的播放代码,用BufferStreamPlayer插件播放效果会好点)。另外(16kbps,16khz)MP3开语音15分钟大概3M的流量,wav、pcm 15分钟要37M多流量。
## WebSocket语音通话聊天demo
[线测试Demo](https://xiangyuecn.github.io/Recorder/)中包含了一个WebSocket版的语音通话聊天的测试功能,测试时需先运行上面这个本地服务器。用两个设备(浏览器打开两个标签也可以)打开demo,勾选实时语音通话聊天对讲,配置好双方标识,连接上服务器即可通话和聊天。手机上测试时,应当使用wss连接,本地服务器里面配置上ssl证书开启wss。

另外除wav、pcm外MP3等格式编码出来的音频的播放时间比PCM原始数据要长一些或短一些,如果涉及到解码或拼接时,这个地方需要注意(如果类型支持,实时处理时使用`takeoffEncodeChunk`选项可完全避免此问题)
对应的源码在 [/assets/zdemo.index.realtime_voice.js](./assets/zdemo.index.realtime_voice.js),实时传输pcm格式的音频数据(未经过任何编码),用BufferStreamPlayer插件播放

![](assets/use_webrtc.png)

## WebRTC语音通话聊天demo
[线测试Demo](https://xiangyuecn.github.io/Recorder/)中还包含了一个WebRTC版的语音通话聊天的测试功能,支持局域网内一对一语音无需服务器。用两个设备(浏览器打开两个标签也可以)打开demo,勾选实时语音通话聊天对讲再切换到WebRTC,按提示交换两个设备的信息即可成功进行P2P连接,然后进行语音。实际使用时数据传输用WebSocket的会简单好多。

早期编写本语音测试的目的在于验证H5录音实时转码、传输的可行性,并验证实时转码mp3格式小片段文件接收后的可播放性。经测试发现:除了移动端可能存在设备性能低下的问题以外,录音后实时转码mp3并传输给对方是可行的,对方接收后播放也能连贯的播放(效果还是要看播放代码写的怎么样,目前没有比较完美的播放代码,用BufferStreamPlayer插件播放效果会好点)。另外(16kbps,16khz)MP3开语音15分钟大概3M的流量,wav、pcm 15分钟要37M多流量。

另外除wav、pcm外MP3等格式编码出来的音频的播放时间比PCM原始数据要长一些或短一些,如果涉及到解码或拼接时,这个地方需要注意(如果类型支持,实时处理时使用`takeoffEncodeChunk`选项可完全避免此问题)。




Expand Down
13 changes: 9 additions & 4 deletions app-support-sample/demo_UniApp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- 支持编译成:H5、Android App、iOS App、微信小程序
- 支持已有的大部分录音格式:mp3、wav、pcm、amr、ogg、g711a、g711u等
- 支持实时处理,包括变速变调、实时上传、ASR语音转文字
- 支持可视化波形显示;可配置回声消除、降噪;注意:不支持通话时录音
- 支持可视化波形显示;可配置回声消除、降噪;**注意:不支持通话时录音**
- 支持离线使用,本组件和配套原生插件均不依赖网络
- App端有配套的[原生录音插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin)可供搭配使用,兼容性和体验更好

Expand Down Expand Up @@ -326,7 +326,7 @@ RecordApp.Start({
,android_audioSource:7 //提供此配置时优先级比audioTrackSet更高,默认值为0

//iOS的AVAudioSession setCategory的withOptions参数值(App搭配原生插件可用),取值请参考本文档下面的iosSetDefault_categoryOptions
//,ios_categoryOptions:0x1|0x4|0x8 //0x8是外放,默认值为0x1|0x4不带0x8是听筒播放,等同于下面的setSpeakerOff
//,ios_categoryOptions:0x1|0x4 //默认值为5(0x1|0x4)
});

//App搭配原生插件时尝试切换听筒播放或外放
Expand Down Expand Up @@ -584,12 +584,17 @@ getInfo 获取插件信息
返回:{ info:"" } //插件信息字符串

setSpeakerOff 切换扬声器外放和听筒播放,随时都可以调用;但需注意打开录音时可能会自动切换播放方式,因此在打开录音后需要明确调用一次切换成你需要的播放方式
参数:{ off:true } //必填,true听筒播放,false扬声器播放
参数:{
off:true //必填,true听筒播放,false扬声器播放,连接耳机时此配置无效
headset:true //选填,默认true耳机播放,false扬声器播放(同时使用手机上的麦克风),连接耳机时此配置生效
//配置场景:当由代码进行主动调用,比如开启回声消除录音时想播放的声音大点,就只提供off:false,这时没连接耳机会从扬声器播放,有耳机就从耳机播放
//配置场景:当类似由用户点击外放按钮时调用,同时提供off:false+headset:false,这时不管有没有耳机,都会从扬声器播放
}
返回:{ } //空对象

iosSetDefault_categoryOptions iOS设置默认值,Android不可调用,为iOS的AVAudioSession setCategory的withOptions参数值;RecordApp.Start开始录音时如果未提供ios_categoryOptions参数,将会使用此默认值,提供了时将赋值给此默认值;setSpeakerOff调用时也会使用到此默认值
参数:{
value:0x1|0x4|0x8 //必填,0x8是外放,默认不带0x8是听筒播放,取值(多选,默认 0x1|0x4):0 什么也不设置,0x1 MixWithOthers,0x2 DuckOthers,0x4 AllowBluetooth,0x8 DefaultToSpeaker,0x11 InterruptSpokenAudioAndMixWithOthers,0x20 AllowBluetoothA2DP,0x40 AllowAirPlay,0x80 OverrideMutedMicrophoneInterruption
value:0x1|0x4 //必填,取值(多选,默认值5=0x1|0x4):0 什么也不设置,0x1 MixWithOthers,0x2 DuckOthers,0x4 AllowBluetooth,0x8 DefaultToSpeaker(不可用,通过setSpeakerOff来切换),0x11 InterruptSpokenAudioAndMixWithOthers,0x20 AllowBluetoothA2DP,0x40 AllowAirPlay,0x80 OverrideMutedMicrophoneInterruption
}
返回:{ } //空对象

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@ export default {
}
,__setSpeakerOff(off){
// #ifdef APP
RecordApp.UniNativeUtsPluginCallAsync("setSpeakerOff",{off:off}).then(()=>{
RecordApp.UniNativeUtsPluginCallAsync("setSpeakerOff",{off:off,headset:off}).then(()=>{
this.reclog("已切换成"+(off?"听筒播放":"外放"),2);
}).catch((e)=>{
this.reclog("切换"+(off?"听筒播放":"外放")+"失败:"+e.message,1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,10 @@ export default {
this.socketIsOpen=false;
this.log("ws已断开");
});
this.socket.onError(()=>{
this.socket.onError((e)=>{
if(sid!=this.SID) return;
this.socketIsOpen=false;
this.log("ws因为错误已断开",1);
this.log("ws因为错误已断开"+e.errMsg,1);
});
this.socket.onOpen(()=>{
if(sid!=this.SID) return;
Expand Down Expand Up @@ -469,8 +469,11 @@ export default {
}
//微信环境,单独创建播放器
,initWxStreamPlay(){
if(this.spWxCtx && this.spWxCtx.state=="running") return;
if(this.spWxCtx) this.spWxCtx.close();
if(this.spWxCtx && this.spWxCtx.state=="running") return;
if(this.spWxCtx){
if(Date.now()-this.spWxCtx.__time<2000) return;//wait running
this.spWxCtx.close();
};
var playBuffers=[], playBufferLen=0;
this.addWxPlayBuffer=(pcm)=>{
playBuffers.push(pcm);
Expand All @@ -483,6 +486,7 @@ export default {
this.log("微信版本太低,无法创建WebAudioContext",1);
return;
}
this.spWxCtx.__time=Date.now();
this.log("微信streamPlay已打开(播放效果一般,听个响)",2);
if(this.spWxTimer)clearInterval(this.spWxTimer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
- 支持编译成:H5、Android App、iOS App、微信小程序
- 支持已有的大部分录音格式:mp3、wav、pcm、amr、ogg、g711a、g711u等
- 支持实时处理,包括变速变调、实时上传、ASR语音转文字
- 支持可视化波形显示;可配置回声消除、降噪;注意:不支持通话时录音
- 支持可视化波形显示;可配置回声消除、降噪;**注意:不支持通话时录音**
- 支持离线使用,本组件和配套原生插件均不依赖网络
- App端有配套的[原生录音插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin)可供搭配使用,兼容性和体验更好

Expand Down Expand Up @@ -319,7 +319,7 @@ RecordApp.Start({
,android_audioSource:7 //提供此配置时优先级比audioTrackSet更高,默认值为0

//iOS的AVAudioSession setCategory的withOptions参数值(App搭配原生插件可用),取值请参考配套原生插件文档中的iosSetDefault_categoryOptions
//,ios_categoryOptions:0x1|0x4|0x8 //0x8是外放,默认值为0x1|0x4不带0x8是听筒播放,等同于下面的setSpeakerOff
//,ios_categoryOptions:0x1|0x4 //默认值为5(0x1|0x4)
});

//App搭配原生插件时尝试切换听筒播放或外放
Expand Down
Binary file modified app-support-sample/demo_android/app-debug.apk.zip
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ public void onReceivedError(WebView view, int errorCode, String description, Str

webView.setBackgroundColor(0xffff6600);

String url="https://xiangyuecn.gitee.io/recorder/app-support-sample/";
String url="https://xiangyuecn.github.io/Recorder/app-support-sample/";
webView.loadUrl(url);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ synchronized private void init(RecordAppJsBridge main_, int sampleRateReq, Callb
}
try {
rec = new AudioRecord(
MediaRecorder.AudioSource.MIC
MediaRecorder.AudioSource.DEFAULT
, SampleRate
, AudioFormat.CHANNEL_IN_MONO
, AudioFormat.ENCODING_PCM_16BIT
Expand Down
8 changes: 4 additions & 4 deletions app-support-sample/demo_ios/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

可以直接copy目录内`RecordAppJsBridge.swift`使用,此文件为核心文件,其他文件都是没什么价值的;支持新开发WKWebView界面,或对已有的WKWebView实例升级支持RecordApp。

**xcode测试项目clone后请修改`PRODUCT_BUNDLE_IDENTIFIER`,不然这个测试id被抢来抢去要闲置7天才能被使用,嫌弃苹果公司工程师水准**
**xcode测试项目clone后请修改`PRODUCT_BUNDLE_IDENTIFIER`,不然这个测试id被抢来抢去要闲置7天才能被使用,太难了**


## 【截图】
Expand All @@ -18,7 +18,7 @@
## 【限制】

- 未做古董版本UIWebView适配,理论上并不需要太大改动就能支持,并不打算进行支持
- 未测试在OC中调用此swift文件,并不打算去写OC代码(学不动
- 未测试在OC中调用此swift文件,并不打算去改写成OC代码(可参考代码自行改写



Expand All @@ -33,9 +33,9 @@ swift收到js发起的prompt弹框请求,解析弹框携带的数据参数,


## 录音接口
接口对应的方法使用的`AVAudioRecorder`来录音,`AVAudioRecorder`会把录音PCM数据写入到文件,因此我们实时从这个文件中读取出数据,然后定时调用`AppJsBridgeRequest.Record`把数据返回给js端即可完成完整的录音功能。
接口对应的方法使用的`AudioUnit`来录音,`AudioUnit`会实时回调录音PCM数据,然后调用`AppJsBridgeRequest.Record`把数据返回给js端即可完成完整的录音功能。

可能是因为`AVAudioRecorder`存在文件写入缓存的原因,数据并非实时的flush到文件的,因此实时发送给js的数据存在300ms左右的滞后;`AudioQueue``AudioUnit`之类的更强大的工具文章又少,代码又多,本质上是因为不会用,所以就成这样了
> 早期版本(2020-11-17)接口对应的方法使用的`AVAudioRecorder`来录音,`AVAudioRecorder`会把录音PCM数据写入到文件,因此我们实时从这个文件中读取出数据,然后定时调用`AppJsBridgeRequest.Record`把数据返回给js端即可完成完整的录音功能;可能是因为`AVAudioRecorder`存在文件写入缓存的原因,数据并非实时的flush到文件的,因此实时发送给js的数据存在300ms左右的滞后,没有AudioUnit的好使

## 需要权限
Expand Down
2 changes: 1 addition & 1 deletion app-support-sample/demo_ios/recorder/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class MainView: UIViewController {
webView.uiDelegate = webUI;
webView.navigationDelegate = webNav;

let url="https://xiangyuecn.gitee.io/recorder/app-support-sample/";
let url="https://xiangyuecn.github.io/Recorder/app-support-sample/";
webView.load(URLRequest(url: URL(string: url)!));


Expand Down
Loading

0 comments on commit ac2e440

Please sign in to comment.