forked from xiangyuecn/Recorder
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e61c596
commit 8ea4e98
Showing
5 changed files
with
896 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ | |
[ [H5 QuickStart](https://xiangyuecn.gitee.io/recorder/QuickStart.html) ] | ||
[ [H5 vue](https://xiangyuecn.gitee.io/recorder/assets/demo-vue/) ] | ||
[ [H5 ts](https://xiangyuecn.gitee.io/recorder/assets/demo-ts/) ] | ||
[ [旧版本测试](https://xiangyuecn.gitee.io/recorder/assets/工具-GitHub页面历史版本访问.html#url=xiangyuecn:[email protected],/) ] | ||
|
||
|
||
|
||
|
@@ -59,7 +60,7 @@ | |
> 如需在Hybrid App WebView内使用(支持iOS、Android,包括uni-app),请参阅本文档下面的【快速使用】中附带的示例,参考示例代码给网页授予录音权限,或直接由App底层提供接口给H5调用([app-support-sample](https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample)目录内有源码)。 | ||
|
||
> *低版本iOS兼容、老旧国产手机自带浏览器上的使用限制等问题和兼容请参阅下面的知识库部分;打开录音后对音频播放的影响、录音中途来电话等问题也参阅下面的知识库。* | ||
> *不同iOS版本下的稳定性、低版本iOS兼容、老旧国产手机自带浏览器上的使用限制等问题和兼容请参阅下面的知识库部分;打开录音后对音频播放的影响、录音中途来电话、手机锁屏对录音的影响等问题也参阅下面的知识库。* | ||
|
||
|
||
|
@@ -85,8 +86,10 @@ | |
14. [【教程】新录音从老录音接续、或录制中途插入音频](https://xiangyuecn.gitee.io/recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.env_in.follow) | ||
15. [【教程】DTMF(电话拨号按键信号)解码、编码](https://xiangyuecn.gitee.io/recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.dtmf.decode_and_encode) | ||
16. [【Demo库】PCM采样率提升](https://xiangyuecn.gitee.io/recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.samplerate.raise) | ||
17. [【测试】WebM格式解析并提取音频](https://xiangyuecn.gitee.io/recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.webm.extract_audio) | ||
18. [【测试】音频可视化相关插件测试](https://xiangyuecn.gitee.io/recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.extensions.visualization) | ||
17. [【Demo库】【信号处理】IIR低通、高通滤波](https://xiangyuecn.gitee.io/recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=dsp.lib.filter.iir) | ||
18. [【测试】【信号处理】FFT频域分析ECharts频谱曲线图](https://xiangyuecn.gitee.io/recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=dsp.test.fft.analysis) | ||
19. [【测试】WebM格式解析并提取音频](https://xiangyuecn.gitee.io/recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.webm.extract_audio) | ||
20. [【测试】音频可视化相关插件测试](https://xiangyuecn.gitee.io/recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.extensions.visualization) | ||
|
||
|
||
### App Demo | ||
|
@@ -464,6 +467,8 @@ iOS其他浏览器|iOS 14.3+|iOS 14.3+ | |
|
||
> 此处已清除7个已知问题,大部分无法解决的问题会随着时间消失;问题主要集中在iOS上,好在这玩意能更新 | ||
*2023-02-22* iPhone 14:有部分开发者反馈iPhone14上关闭录音后再次打开录音,会出现无法录音的情况,目前并不清楚是只有iPhone14上有问题,还是iOS16均有问题;估计是新的WebKit改了相关源码印度阿三没有测试,js没办法解决此问题,静候iOS更新,也许下一个系统更新就自动修复了;建议针对iOS环境,全局只open一次,不要close,挂在那里录音,可减少iOS系统问题带来的影响(负优化+耗电)。 | ||
|
||
*2020-04-26* Safari Bug:据QQ群内`1048506792`、`190451148`开发者反馈研究发现,IOS ?-13.X Safari内打开录音后,如果切换到了其他标签、或其他App并且播放了任何声音,此时将会中断已打开的录音(系统级的?),切换回正在录音的页面,这个页面的录音功能将会彻底失效,并且刷新也无法恢复录音;表现为关闭录音后再次打开录音,能够正常获得权限,但浏览器返回的采集到的音频为静默的PCM,此时地址栏也并未显示出麦克风图标,刷新这个标签也也是一样不能正常获得录音,只有关掉此标签新打开页面才可正常录音。如果打开录音后关闭了录音,然后切换到其他标签或App播放声音,然后返回录音页面,不会出现此问题。此为Safari的底层Bug。使用长按录音类似的用户交互可大幅度避免踩到这坨翔。 | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,309 @@ | ||
/* | ||
时域转频域,快速傅里叶变换(FFT) | ||
https://github.com/xiangyuecn/Recorder | ||
相对于Recorder.LibFFT(extensions/lib.fft.js),此版本计算更准确些 | ||
var fft=Recorder.LibFFT_Exact(bufferSize) | ||
bufferSize取值2的n次方 | ||
fft.bufferSize 实际采用的bufferSize | ||
fft.transform(inBuffer) 计算得到频域值 | ||
inBuffer:[Int16,...] 数组长度必须是bufferSize | ||
返回[Float64(Long),...],长度为bufferSize/2 | ||
LibFFT 得到的是模的平方,用Math.sqrt(v)*2转一下就和这个的差不多 | ||
fft.toDBFS(outBuffer) 计算音量,单位dBFS(满刻度相对电平) | ||
outBuffer:[Float64,...] transform返回的结果 | ||
返回[-100~0,...] (最大值0dB,最小值-100代替-∞) | ||
*/ | ||
Recorder.LibFFT_Exact=function(bufferSize){ | ||
"use strict"; | ||
|
||
/* | ||
indutny/fft.js (MIT License) | ||
https://github.com/indutny/fft.js/blob/4a18cf88fcdbd4ad5acca6eaea06a0b462047835/lib/fft.js | ||
*/ | ||
function FFT(size) { | ||
this.size = size | 0; | ||
if (this.size <= 1 || (this.size & (this.size - 1)) !== 0) | ||
throw new Error('FFT size must be a power of two and bigger than 1'); | ||
|
||
this._csize = size << 1; | ||
|
||
// NOTE: Use of `var` is intentional for old V8 versions | ||
var table = new Array(this.size * 2); | ||
for (var i = 0; i < table.length; i += 2) { | ||
var angle = Math.PI * i / this.size; | ||
table[i] = Math.cos(angle); | ||
table[i + 1] = -Math.sin(angle); | ||
} | ||
this.table = table; | ||
|
||
// Find size's power of two | ||
var power = 0; | ||
for (var t = 1; this.size > t; t <<= 1) | ||
power++; | ||
|
||
// Calculate initial step's width: | ||
// * If we are full radix-4 - it is 2x smaller to give inital len=8 | ||
// * Otherwise it is the same as `power` to give len=4 | ||
this._width = power % 2 === 0 ? power - 1 : power; | ||
|
||
// Pre-compute bit-reversal patterns | ||
this._bitrev = new Array(1 << this._width); | ||
for (var j = 0; j < this._bitrev.length; j++) { | ||
this._bitrev[j] = 0; | ||
for (var shift = 0; shift < this._width; shift += 2) { | ||
var revShift = this._width - shift - 2; | ||
this._bitrev[j] |= ((j >>> shift) & 3) << revShift; | ||
} | ||
} | ||
|
||
this._out = null; | ||
this._data = null; | ||
this._inv = 0; | ||
} | ||
|
||
FFT.prototype.realTransform = function(out, data) { | ||
if (out === data) | ||
throw new Error('Input and output buffers must be different'); | ||
|
||
this._out = out; | ||
this._data = data; | ||
this._inv = 0; | ||
this._realTransform4(); | ||
this._out = null; | ||
this._data = null; | ||
}; | ||
|
||
// Real input radix-4 implementation | ||
FFT.prototype._realTransform4 = function() { | ||
var out = this._out; | ||
var size = this._csize; | ||
|
||
// Initial step (permute and transform) | ||
var width = this._width; | ||
var step = 1 << width; | ||
var len = (size / step) << 1; | ||
|
||
var outOff; | ||
var t; | ||
var bitrev = this._bitrev; | ||
if (len === 4) { | ||
for (outOff = 0, t = 0; outOff < size; outOff += len, t++) { | ||
var off = bitrev[t]; | ||
this._singleRealTransform2(outOff, off >>> 1, step >>> 1); | ||
} | ||
} else { | ||
// len === 8 | ||
for (outOff = 0, t = 0; outOff < size; outOff += len, t++) { | ||
var off = bitrev[t]; | ||
this._singleRealTransform4(outOff, off >>> 1, step >>> 1); | ||
} | ||
} | ||
|
||
// Loop through steps in decreasing order | ||
var inv = this._inv ? -1 : 1; | ||
var table = this.table; | ||
for (step >>= 2; step >= 2; step >>= 2) { | ||
len = (size / step) << 1; | ||
var halfLen = len >>> 1; | ||
var quarterLen = halfLen >>> 1; | ||
var hquarterLen = quarterLen >>> 1; | ||
|
||
// Loop through offsets in the data | ||
for (outOff = 0; outOff < size; outOff += len) { | ||
for (var i = 0, k = 0; i <= hquarterLen; i += 2, k += step) { | ||
var A = outOff + i; | ||
var B = A + quarterLen; | ||
var C = B + quarterLen; | ||
var D = C + quarterLen; | ||
|
||
// Original values | ||
var Ar = out[A]; | ||
var Ai = out[A + 1]; | ||
var Br = out[B]; | ||
var Bi = out[B + 1]; | ||
var Cr = out[C]; | ||
var Ci = out[C + 1]; | ||
var Dr = out[D]; | ||
var Di = out[D + 1]; | ||
|
||
// Middle values | ||
var MAr = Ar; | ||
var MAi = Ai; | ||
|
||
var tableBr = table[k]; | ||
var tableBi = inv * table[k + 1]; | ||
var MBr = Br * tableBr - Bi * tableBi; | ||
var MBi = Br * tableBi + Bi * tableBr; | ||
|
||
var tableCr = table[2 * k]; | ||
var tableCi = inv * table[2 * k + 1]; | ||
var MCr = Cr * tableCr - Ci * tableCi; | ||
var MCi = Cr * tableCi + Ci * tableCr; | ||
|
||
var tableDr = table[3 * k]; | ||
var tableDi = inv * table[3 * k + 1]; | ||
var MDr = Dr * tableDr - Di * tableDi; | ||
var MDi = Dr * tableDi + Di * tableDr; | ||
|
||
// Pre-Final values | ||
var T0r = MAr + MCr; | ||
var T0i = MAi + MCi; | ||
var T1r = MAr - MCr; | ||
var T1i = MAi - MCi; | ||
var T2r = MBr + MDr; | ||
var T2i = MBi + MDi; | ||
var T3r = inv * (MBr - MDr); | ||
var T3i = inv * (MBi - MDi); | ||
|
||
// Final values | ||
var FAr = T0r + T2r; | ||
var FAi = T0i + T2i; | ||
|
||
var FBr = T1r + T3i; | ||
var FBi = T1i - T3r; | ||
|
||
out[A] = FAr; | ||
out[A + 1] = FAi; | ||
out[B] = FBr; | ||
out[B + 1] = FBi; | ||
|
||
// Output final middle point | ||
if (i === 0) { | ||
var FCr = T0r - T2r; | ||
var FCi = T0i - T2i; | ||
out[C] = FCr; | ||
out[C + 1] = FCi; | ||
continue; | ||
} | ||
|
||
// Do not overwrite ourselves | ||
if (i === hquarterLen) | ||
continue; | ||
|
||
// In the flipped case: | ||
// MAi = -MAi | ||
// MBr=-MBi, MBi=-MBr | ||
// MCr=-MCr | ||
// MDr=MDi, MDi=MDr | ||
var ST0r = T1r; | ||
var ST0i = -T1i; | ||
var ST1r = T0r; | ||
var ST1i = -T0i; | ||
var ST2r = -inv * T3i; | ||
var ST2i = -inv * T3r; | ||
var ST3r = -inv * T2i; | ||
var ST3i = -inv * T2r; | ||
|
||
var SFAr = ST0r + ST2r; | ||
var SFAi = ST0i + ST2i; | ||
|
||
var SFBr = ST1r + ST3i; | ||
var SFBi = ST1i - ST3r; | ||
|
||
var SA = outOff + quarterLen - i; | ||
var SB = outOff + halfLen - i; | ||
|
||
out[SA] = SFAr; | ||
out[SA + 1] = SFAi; | ||
out[SB] = SFBr; | ||
out[SB + 1] = SFBi; | ||
} | ||
} | ||
} | ||
}; | ||
|
||
// radix-2 implementation | ||
// | ||
// NOTE: Only called for len=4 | ||
FFT.prototype._singleRealTransform2 = function(outOff, off, step) { | ||
var out = this._out; | ||
var data = this._data; | ||
|
||
var evenR = data[off]; | ||
var oddR = data[off + step]; | ||
|
||
var leftR = evenR + oddR; | ||
var rightR = evenR - oddR; | ||
|
||
out[outOff] = leftR; | ||
out[outOff + 1] = 0; | ||
out[outOff + 2] = rightR; | ||
out[outOff + 3] = 0; | ||
}; | ||
|
||
// radix-4 | ||
// | ||
// NOTE: Only called for len=8 | ||
FFT.prototype._singleRealTransform4 = function(outOff, off, step) { | ||
var out = this._out; | ||
var data = this._data; | ||
var inv = this._inv ? -1 : 1; | ||
var step2 = step * 2; | ||
var step3 = step * 3; | ||
|
||
// Original values | ||
var Ar = data[off]; | ||
var Br = data[off + step]; | ||
var Cr = data[off + step2]; | ||
var Dr = data[off + step3]; | ||
|
||
// Pre-Final values | ||
var T0r = Ar + Cr; | ||
var T1r = Ar - Cr; | ||
var T2r = Br + Dr; | ||
var T3r = inv * (Br - Dr); | ||
|
||
// Final values | ||
var FAr = T0r + T2r; | ||
|
||
var FBr = T1r; | ||
var FBi = -T3r; | ||
|
||
var FCr = T0r - T2r; | ||
|
||
var FDr = T1r; | ||
var FDi = T3r; | ||
|
||
out[outOff] = FAr; | ||
out[outOff + 1] = 0; | ||
out[outOff + 2] = FBr; | ||
out[outOff + 3] = FBi; | ||
out[outOff + 4] = FCr; | ||
out[outOff + 5] = 0; | ||
out[outOff + 6] = FDr; | ||
out[outOff + 7] = FDi; | ||
}; | ||
|
||
|
||
//**封装调用** | ||
var FFT_N_LOG=Math.round(Math.log(bufferSize)/Math.log(2)); | ||
var FFT_N = 1 << FFT_N_LOG; | ||
|
||
var fft=new FFT(FFT_N); | ||
var fftOut=new Float64Array(FFT_N*2); | ||
|
||
var getModulus=function(inBuffer){ | ||
fft.realTransform(fftOut, inBuffer); | ||
var L=FFT_N/2, outBuffer=new Float64Array(L); | ||
for(var i=0,j=0;i<L;i++,j+=2){ | ||
//fftOut: 使用左侧一半数据,且隔一个取值,取够1/2的fftSize | ||
outBuffer[i]=~~Math.max(Math.abs(fftOut[j]),Math.abs(fftOut[j+1])); | ||
} | ||
return outBuffer; | ||
}; | ||
var toDBFS=function(outBuffer){ | ||
var arr=[]; | ||
for(var i=0,L=outBuffer.length,v;i<L;i++){ | ||
v=outBuffer[i]/FFT_N*4; //测试得出的,比较接近0x7FFF | ||
v=20*Math.log(Math.max(0.1,v)/0x7FFF)/Math.log(10); | ||
arr.push(Math.min(0,Math.max(-100,Math.round(v)))); | ||
} | ||
return arr; | ||
}; | ||
|
||
return {transform:getModulus,toDBFS:toDBFS,bufferSize:FFT_N}; | ||
}; |
Oops, something went wrong.