diff --git a/README.md b/README.md index a984a38..7b99354 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,7 @@ $.ajax({ 6. [【教程】实时多路音频混音](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.mix_multiple) 7. [【教程】变速变调音频转换](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.sonic.transform) 8. [【Demo库】PCM采样率提升](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.samplerate.raise) +9. [【测试】音频可视化相关扩展测试](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.extensions.visualization) @@ -563,6 +564,11 @@ Recorder({type:"aac"}) # :open_book:扩展 在`src/extensions`目录内为扩展支持库,这些扩展库默认都没有合并到生成代码中,需单独引用(`dist`或`src`中的)才能使用。 +【附】部分扩展使用效果图: + +![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/use_wave.gif) + + ## `WaveView`扩展 [waveview.js](https://github.com/xiangyuecn/Recorder/blob/master/src/extensions/waveview.js),4kb大小源码,录音时动态显示波形,具体样子参考演示地址页面。此扩展参考[MCVoiceWave](https://github.com/HaloMartin/MCVoiceWave)库编写的,具体代码在`https://github.com/HaloMartin/MCVoiceWave/blob/f6dc28975fbe0f7fc6cc4dbc2e61b0aa5574e9bc/MCVoiceWave/MCVoiceWaveView.m`中。 @@ -581,7 +587,6 @@ rec.open(function(){ }); ``` -![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/use_wave.png) [​](?RefEnd) ### 【构造】wave=Recorder.WaveView(set) @@ -611,12 +616,48 @@ set={ 输入音频数据,更新波形显示,这个方法调用的越快,波形越流畅。pcmData `[Int16,...]` 一维数组,为当前的录音数据片段,其他参数和`onProcess`回调相同。 +## `WaveSurferView`扩展 +[wavesurfer.view.js](https://github.com/xiangyuecn/Recorder/blob/master/src/extensions/wavesurfer.view.js),7kb大小源码,音频可视化波形显示,具体样子参考演示地址页面。 + +此扩展的使用方式和`WaveView`扩展完全相同,请参考上面的`WaveView`来使用;本扩展的波形绘制直接简单的使用PCM的采样数值大小来进行线条的绘制,同一段音频绘制出的波形和Audition内显示的波形外观上几乎没有差异。 + +### 【构造】surfer=Recorder.WaveSurferView(set) +构造函数,`set`参数为配置对象,默认配置值如下: +``` javascript +set={ + elem:"css selector" //自动显示到dom,并以此dom大小为显示大小 + //或者配置显示大小,手动把frequencyObj.elem显示到别的地方 + ,width:0 //显示宽度 + ,height:0 //显示高度 + + //以上配置二选一 + + scale:2 //缩放系数,应为正整数,使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊 + + ,fps:50 //绘制帧率,不可过高,50-60fps运动性质动画明显会流畅舒适,实际显示帧率达不到这个值也并无太大影响 + + ,duration:2500 //当前视图窗口内最大绘制的波形的持续时间,此处决定了移动速率 + ,direction:1 //波形前进方向,取值:1由左往右,-1由右往左 + ,position:0 //绘制位置,取值-1到1,-1为最底下,0为中间,1为最顶上,小数为百分比 + + ,centerHeight:1 //中线基础粗细,如果为0不绘制中线,position=±1时应当设为0 + + //波形颜色配置:[位置,css颜色,...] 位置: 取值0.0-1.0之间 + ,linear:[0,"rgba(0,187,17,1)",0.7,"rgba(255,215,0,1)",1,"rgba(255,102,0,1)"] + ,lineColor:"" //中线css颜色,留空取波形第一个渐变颜色 +} +``` + +### 【方法】surfer.input(pcmData,powerLevel,sampleRate) +输入音频数据,更新波形显示。pcmData `[Int16,...]` 一维数组,为当前的录音数据片段,其他参数和`onProcess`回调相同。 + + + ## `FrequencyHistogramView`扩展 [frequency.histogram.view.js](https://github.com/xiangyuecn/Recorder/blob/master/src/extensions/frequency.histogram.view.js) + [lib.fft.js](https://github.com/xiangyuecn/Recorder/blob/master/src/extensions/lib.fft.js),12kb大小源码,音频可视化频率直方图显示,具体样子参考演示地址页面。此扩展核心算法参考Java开源库[jmp123](https://sourceforge.net/projects/jmp123/files/)的代码编写的,`jmp123`版本`0.3`;直方图特意优化主要显示0-10khz语音部分,其他高频显示区域较小,不适合用来展示音乐频谱。 此扩展的使用方式和`WaveView`扩展完全相同,请参考上面的`WaveView`来使用;请注意:必须同时引入`lib.fft.js`才能正常工作。 -![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/use_histogram.png) ### 【构造】histogram=Recorder.FrequencyHistogramView(set) 构造函数,`set`参数为配置对象,默认配置值如下: diff --git a/assets/npm-home/README.md b/assets/npm-home/README.md index 1ed83d7..0d4f060 100644 --- a/assets/npm-home/README.md +++ b/assets/npm-home/README.md @@ -40,19 +40,25 @@ npm install recorder-core ## WaveView的调用方式 -直接通过Recorder.WaveView调用即可,录音时动态显示波形,详细的使用请参考下面详细的README。 +引入`src/extensions/waveview.js`,再通过Recorder.WaveView调用即可,录音时动态显示波形,详细的使用请参考下面详细的README。 @@Ref README.WaveView.Codes@@ +【附】部分扩展使用效果图: -## FrequencyHistogramView的调用方式 -直接通过Recorder.FrequencyHistogramView调用即可,音频可视化频率直方图显示,详细的使用请参考下面详细的README。 +![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/use_wave.gif) + + +## WaveSurferView的调用方式 +引入`src/extensions/wavesurfer.view.js`,再通过Recorder.WaveSurferView调用即可,录音时动态显示波形,详细的使用请参考下面详细的README。 -![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/use_histogram.png) + +## FrequencyHistogramView的调用方式 +引入`src/extensions/frequency.histogram.view.js`+`lib.fft.js`,再通过Recorder.FrequencyHistogramView调用即可,音频可视化频率直方图显示,详细的使用请参考下面详细的README。 ## Sonic的调用方式 -直接通过Recorder.Sonic调用即可,音频变速变调转换,详细的使用请参考下面详细的README。 +引入`src/extensions/sonic.js`,再通过Recorder.Sonic调用即可,音频变速变调转换,详细的使用请参考下面详细的README。 ## RecordApp的调用方式 diff --git a/assets/npm-home/hash-history.txt b/assets/npm-home/hash-history.txt index 9d79c3c..45dad32 100644 --- a/assets/npm-home/hash-history.txt +++ b/assets/npm-home/hash-history.txt @@ -1,4 +1,8 @@ [ + { + "sha1": "d494fe71ad5175c130c56b1ce09d4953b0fc045a", + "time": "2020-1-17 16:12:00" + }, { "sha1": "d7949810ec706e70159f0e09860b6f627197a29d", "time": "2020-1-16 16:19:04" @@ -14,9 +18,5 @@ { "sha1": "bad5a11c405b2766887f5a68bab7c0ce6ea14013", "time": "2020-1-14 17:40:50" - }, - { - "sha1": "e3d0df0e66aa924cf04a56dafc858c40c6054bf6", - "time": "2020-1-14 03:12:04" } ] \ No newline at end of file diff --git a/assets/runtime-codes/test.extensions.visualization.js b/assets/runtime-codes/test.extensions.visualization.js new file mode 100644 index 0000000..92cd99f --- /dev/null +++ b/assets/runtime-codes/test.extensions.visualization.js @@ -0,0 +1,372 @@ +/****************** +《【测试】音频可视化相关扩展测试》 +作者:高坚果 +时间:2020-1-17 11:59:47 + +本测试将测试Recorder所有音频可视化相关的扩展。 +******************/ +var waveConfigs={ + WaveView:[ + {testTitle:"颜色" + ,linear1:[0,"#0b1",1,"#0b1"] + ,linear2:[0,"#0b1",1,"#0b1"] + ,linearBg:[0,"gold",1,"#0b1"]} + ] + ,WaveSurferView:[ + {testTitle:"底部显示+慢速" + ,position:-1 + ,duration:10000 + ,linear:[0,"#666",1,"#666"]} + ,{testTitle:"由右到左+快速" + ,direction:-1 + ,duration:1000 + ,linear:[0,"#f00",1,"#f00"]} + ] + ,FrequencyHistogramView:[ + {testTitle:"中部显示" + ,lineCount:70 + ,position:0 + ,minHeight:1 + ,stripeEnable:false + ,linear:[0,"#06c",1,"#06c"]} + ,{testTitle:"顶部显示+粗大" + ,lineCount:15 + ,position:1 + ,lineWidth:999 + ,stripeEnable:false + ,linear:[0,"#ab00ff",1,"#ab00ff"]} + ] +}; +var waveStore={}; +var visualizationInput=function(buffers,powerLevel,duration,sampleRate){ + var buffer=buffers[buffers.length-1]; + for(var k in waveStore){ + var o=waveStore[k]; + if(o&&o.wave){ + (function(o){ + setTimeout(function(){ + o.wave.input(buffer,powerLevel,sampleRate); + }); + })(o); + }; + }; +}; +var visualizationCreate=function(){ + if(waveStore.width==ctrlSet.width&&waveStore.height==ctrlSet.height){ + //配置无变化 + return; + }; + waveStore={width:ctrlSet.width,height:ctrlSet.height}; + var box=$(".visualizationBox").html(""); + + for(var k in Recorder){ + var o=Recorder[k]; + if(!(/view/i.test(k)&&o&&o.prototype&&o.prototype.input)){ + continue; + }; + box.append('
'+k+'
'); + + var configs=$.merge([{testTitle:"默认配置"}],waveConfigs[k]); + for(var i=0;i\ +
'+config.testTitle+'
\ +
\ +'); + + //实例化可视化对象 + config.elem="."+cls; + waveStore[cls]={wave:o(config),config:config}; + }; + }; +}; + + + + +//******音频数据源,采集原始音频用的****** +//显示控制按钮 +Runtime.Ctrls([ + {name:"打开音频输入",click:"recStart"} + ,{name:"结束",click:"recStop"} + + ,{html:'
音效控制\ +\ +
'} + + ,{name:"麦克风静音",click:"muteChange",cls:"mixMinBtn mixMinBtnOff mixBtn-mute"} + ,{name:"BGM静音",click:"muteChange('BGM');Date.now",cls:"mixMinBtn mixMinBtnOff mixBtn-muteBGM"} + ,{name:"爆笑音效",click:"bgmSet('xiao');Date.now",cls:"mixMinBtn mixMinBtnOff mixBtn-xiao"} + ,{name:"晕倒音效",click:"bgmSet('yun');Date.now",cls:"mixMinBtn mixMinBtnOff mixBtn-yun"} + ,{name:"转场音效",click:"bgmSet('scene');Date.now",cls:"mixMinBtn mixMinBtnOff mixBtn-scene"} + + ,{choiceFile:{ + title:"替换混音BGM" + ,process:function(fileName,arrayBuffer,filesCount,fileIdx,endCall){ + if(fileIdx==0){ + loadWait=0; + musics=[]; + }; + decodeAudio(fileName,arrayBuffer,function(){ + if(fileIdx+1>=filesCount){ + Runtime.Log("素材替换完毕,可以开始录音了",2); + }; + endCall(); + }); + } + }} + + ,{html:'
音频可视化
\ +
\ +\ +
Width: 显示区域宽度
\ +
Height: 显示区域高度
\ +
\ +\ +
'} +]); + +var ctrlSet={}; +$(".ctrlInput").bind("change",function(e){ + ctrlSet[$(e.target).attr("key")]=+e.target.value; + setTimeout(function(){visualizationCreate()},300); +}); +$(".ctrlRange").bind("change",function(e){ + $(e.target).parent().find(".ctrlInput").val(e.target.value).change(); +}).change(); +var resetCtrl=function(){ + $(".ctrlRange").each(function(k,v){ + $(v).val($(v).attr("value")).change(); + }); +}; + + +//加载录音框架 +Runtime.Import([ + {url:RootFolder+"/src/recorder-core.js",check:function(){return !window.Recorder}} + ,{url:RootFolder+"/src/engine/mp3.js",check:function(){return !Recorder.prototype.mp3}} + ,{url:RootFolder+"/src/engine/mp3-engine.js",check:function(){return !Recorder.lamejs}} + + ,{url:RootFolder+"/assets/runtime-codes/fragment.playbuffer.js",check:function(){return !window.DemoFragment||!DemoFragment.PlayBuffer}}//引入DemoFragment.PlayBuffer +]); + + +//调用录音 +var rec; +function recStart(){ + voiceSet={}; + $(".mixMinBtn").addClass("mixMinBtnOff"); + var mixChunk={}; + + rec=Recorder({ + type:"mp3" + ,sampleRate:32000 + ,bitRate:96 + ,onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate){ + mixChunk=mixProcess(buffers,bufferSampleRate,mixChunk); + powerLevel=mixChunk.powerLevel; + + Runtime.Process(buffers,powerLevel,bufferDuration,bufferSampleRate); + + visualizationInput(buffers,powerLevel,bufferDuration,bufferSampleRate); + } + }); + var t=setTimeout(function(){ + Runtime.Log("无法录音:权限请求被忽略(超时假装手动点击了确认对话框)",1); + },8000); + + rec.open(function(){//打开麦克风授权获得相关资源 + clearTimeout(t); + rec.start();//开始录音 + Runtime.Log("正在进行录音,随时猛击音效控制按钮添加音效..."); + },function(msg,isUserNotAllow){//用户拒绝未授权或不支持 + clearTimeout(t); + Runtime.Log((isUserNotAllow?"UserNotAllow,":"")+"无法录音:"+msg, 1); + }); +}; +function recStop(){ + Runtime.Log("正在进行编码..."); + rec.stop(function(blob,duration){ + Runtime.LogAudio(blob,duration,rec,"混音结果"); + rec=null; + },function(msg){ + Runtime.Log("录音失败:"+msg, 1); + },1); +}; + + + + + + + + +//copy自teach.realtime.mix_multiple.js + +var musics=[];//混音BGM素材列表 +var musicBGs={};//音效素材列表 +var musicPercent=1/1;//混音素材音量降低这么多,免得把主要的录音混的听不清 + +//*****简单混音******* +var Mix=function(buffer,sampleRate,mute,bgm,posFloat,loop){ + var step=bgm.sampleRate/sampleRate; + var curInt=-1,sum=0; + for(var j=0;jcurInt){ + var data_mix,data1=buffer[j],data2=(bgm.pcm[cur]||0)*musicPercent; + + //简单混音算法 https://blog.csdn.net/dancing_night/article/details/53080819 + if(data1<0 && data2<0){ + data_mix = data1+data2 - (data1 * data2 / -0x7FFF); + }else{ + data_mix = data1+data2 - (data1 * data2 / 0x7FFF); + }; + + buffer[j]=data_mix; + }; + curInt=cur; + posFloat+=step; + if(loop && posFloat>=bgm.pcm.length){ + posFloat=0; + curInt=-1;//洗脑循环 直接回到开头可否 ???? + }; + + sum+=Math.abs(buffer[j]); + }; + return {pos:posFloat,sum:sum}; +}; +var mixProcess=function(buffers,sampleRate,chunk){ + var idx=chunk.idx||0; + for(;idx=bgm.pcm.length){ + bgms.splice(i,1); + i--; + }; + }; + + //播放bgmBuffer,录制端能听到实时bgm反馈 + DemoFragment.PlayBuffer(chunk,bgmBuffer,sampleRate); + + //将bgmBuffer混入buffer中 + var info=Mix(buffer,sampleRate,voiceSet.mute,{pcm:bgmBuffer,sampleRate:sampleRate},0,false); + + chunk.powerLevel=Recorder.PowerLevel(info.sum,buffer.length); +}; + +var voiceSet={}; +var muteChange=function(bgm){ + if(!rec){ + Runtime.Log("未开始混音",1); + return + }; + bgm=bgm||""; + voiceSet["mute"+bgm]=!voiceSet["mute"+bgm]; + $(".mixBtn-mute"+bgm)[voiceSet["mute"+bgm]?"removeClass":"addClass"]("mixMinBtnOff"); +}; +var bgmSet=function(bgm){ + if(!rec){ + Runtime.Log("未开始混音",1); + return + }; + var bgms=voiceSet.bgms=voiceSet.bgms||[]; + bgms.push({key:bgm}); +}; + + +//*****加载和解码素材******** +var loadWait=0; +var load=function(name,bgName,call){ + Runtime.Log("开始加载混音音频素材"+name+",请勿操作..."); + loadWait++; + var xhr=new XMLHttpRequest(); + xhr.onloadend=function(){ + if(xhr.status==200){ + loadWait--; + decodeAudio(name,xhr.response,call,bgName); + }else{ + Runtime.Log("加载音频失败["+xhr.status+"]:"+name,1); + }; + }; + xhr.open("GET",RootFolder+"/assets/audio/"+name,true); + //xhr.timeout=16000; + xhr.responseType="arraybuffer"; + xhr.send(); +}; +var decodeAudio=function(name,arr,call,bgName){ + Runtime.DecodeAudio(name,arr,function(data){ + Runtime.LogAudio(data.srcBlob,data.duration,{set:data},"已解码"+name); + + if(bgName){ + musicBGs[bgName]={pcm:data.data,sampleRate:data.sampleRate}; + }else{ + musics.push({pcm:data.data,sampleRate:data.sampleRate}); + }; + call(); + },function(msg){ + Runtime.Log(msg,1); + call(); + }); +}; +var loadAll=function(){ + load("music-在人间-张韶涵.mp3",0,function(){ + load("bgm-爆笑.mp3","xiao",function(){ + load("bgm-晕倒.mp3","yun",function(){ + load("bgm-转场.mp3","scene",function(){ + Runtime.Log("待混音音频素材已准备完毕,可以开始录音了",2); + }); + }); + }); + }); +}; + +//加载素材 +setTimeout(loadAll); \ No newline at end of file diff --git a/assets/use_histogram.png b/assets/use_histogram.png deleted file mode 100644 index 8b8fd86..0000000 Binary files a/assets/use_histogram.png and /dev/null differ diff --git a/assets/use_wave.gif b/assets/use_wave.gif new file mode 100644 index 0000000..bdd5a9c Binary files /dev/null and b/assets/use_wave.gif differ diff --git a/assets/use_wave.png b/assets/use_wave.png deleted file mode 100644 index c02ecde..0000000 Binary files a/assets/use_wave.png and /dev/null differ diff --git "a/assets/\345\267\245\345\205\267-\344\273\243\347\240\201\350\277\220\350\241\214\345\222\214\351\235\231\346\200\201\345\210\206\345\217\221Runtime.html" "b/assets/\345\267\245\345\205\267-\344\273\243\347\240\201\350\277\220\350\241\214\345\222\214\351\235\231\346\200\201\345\210\206\345\217\221Runtime.html" index 0333e4c..a4cc707 100644 --- "a/assets/\345\267\245\345\205\267-\344\273\243\347\240\201\350\277\220\350\241\214\345\222\214\351\235\231\346\200\201\345\210\206\345\217\221Runtime.html" +++ "b/assets/\345\267\245\345\205\267-\344\273\243\347\240\201\350\277\220\350\241\214\345\222\214\351\235\231\346\200\201\345\210\206\345\217\221Runtime.html" @@ -1080,6 +1080,7 @@ ,{n:"【教程】变速变调音频转换",k:"teach.sonic.transform"} ,{n:"【Demo库】PCM采样率提升",k:"lib.samplerate.raise"} +,{n:"【测试】音频可视化相关扩展测试",k:"test.extensions.visualization"} ]; diff --git a/dist/extensions/wavesurfer.view.js b/dist/extensions/wavesurfer.view.js index 3e056e2..a01fa3c 100644 --- a/dist/extensions/wavesurfer.view.js +++ b/dist/extensions/wavesurfer.view.js @@ -3,4 +3,4 @@ https://github.com/xiangyuecn/Recorder src: extensions/wavesurfer.view.js */ -!function(){"use strict";var e=function(e){return new t(e)},t=function(e){var t=this,a={scale:2,fps:50,duration:2500,direction:1,position:0,centerHeight:1,linear:[0,"rgba(0,187,17,1)",.7,"rgba(255,215,0,1)",1,"rgba(255,102,0,1)"],lineColor:""};for(var r in e)a[r]=e[r];t.set=e=a;var i=e.elem;i&&("string"==typeof i?i=document.querySelector(i):i.length&&(i=i[0])),i&&(e.width=i.offsetWidth,e.height=i.offsetHeight);var n=e.scale,o=e.width*n,l=e.height*n,h=t.elem=document.createElement("div"),c=["","transform-origin:0 0;","transform:scale("+1/n+");"];h.innerHTML='
';var s=t.canvas=h.querySelector("canvas");t.ctx=s.getContext("2d");s.width=o,s.height=l;var d=t.canvas2=document.createElement("canvas");t.ctx2=d.getContext("2d");d.width=2*o,d.height=l,i&&(i.innerHTML="",i.appendChild(h)),t.x=0};t.prototype=e.prototype={genLinear:function(e,t,a,r){for(var i=e.createLinearGradient(0,a,0,r),n=0;n
';var s=t.canvas=h.querySelector("canvas");t.ctx=s.getContext("2d");s.width=o,s.height=l;var d=t.canvas2=document.createElement("canvas");t.ctx2=d.getContext("2d");d.width=2*o,d.height=l,i&&(i.innerHTML="",i.appendChild(h)),t.x=0};t.prototype=e.prototype={genLinear:function(e,t,a,r){for(var i=e.createLinearGradient(0,a,0,r),n=0;n