UNPKG

recorder-core

Version:

Recorder库: html5 js 录音 mp3 wav ogg webm amr g711a g711u 格式,支持pc和Android、iOS部分浏览器、Hybrid App(提供Android iOS App源码)、微信,提供ASR语音识别转文字 H5版语音通话聊天示例 DTMF编码解码

367 lines (326 loc) 10.4 kB
/* ogg编码器,beta版,需带上src/engine/beta-ogg-engine.js引擎使用 https://github.com/xiangyuecn/Recorder 当然最佳推荐使用mp3、wav格式,代码也是优先照顾这两种格式 浏览器支持情况 https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats */ (function(factory){ var browser=typeof window=="object" && !!window.document; var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面 var rec=win.Recorder,ni=rec.i18n; factory(rec,ni,ni.$T,browser); }(function(Recorder,i18n,$T,isBrowser){ "use strict"; Recorder.prototype.enc_ogg={ stable:true,takeEC:"slow" ,getTestMsg:function(){ return $T("O8Gn::Ogg Vorbis,比特率取值16-100kbps,采样率取值无限制"); } }; var ImportEngineErr=function(){ return $T.G("NeedImport-2",["beta-ogg.js","src/engine/beta-ogg-engine.js"]); }; //是否支持web worker var HasWebWorker=isBrowser && typeof Worker=="function"; //*******标准UI线程转码支持函数************ Recorder.prototype.ogg=function(res,True,False){ var This=this,set=This.set,size=res.length,bitRate=set.bitRate; if(!Recorder.OggVorbisEncoder){ False(ImportEngineErr()); return; }; //优先采用worker编码,非worker时用老方法提供兼容 if(HasWebWorker){ var ctx=This.ogg_start(set); if(ctx){ if(ctx.isW){ This.ogg_encode(ctx,res); This.ogg_complete(ctx,True,False,1); return; } This.ogg_stop(ctx); }; }; var bitV=GetBitRate(bitRate); set.bitRate=bitV.bitRate; var ogg = new Recorder.OggVorbisEncoder(set.sampleRate, 1, bitV.val); var blockSize=set.sampleRate; var memory=new Uint8Array(500000), mOffset=0; var idx=0,isFlush=0; var run=function(){ try{ if(idx<size){ var buf=ogg.encode([res.subarray(idx,idx+blockSize)]); }else{ isFlush=1; var buf=ogg.flush(); }; }catch(e){ //精简代码调用了abort console.error(e); if(!isFlush) try{ ogg.flush() }catch(r){ console.error(r) } False("Ogg Encoder: "+e.message); return; }; if(buf.length>0){ var bufLen=buf.length; if(mOffset+bufLen>memory.length){ var tmp=new Uint8Array(memory.length+Math.max(500000,bufLen)); tmp.set(memory.subarray(0, mOffset)); memory=tmp; } memory.set(buf,mOffset); mOffset+=bufLen; }; if(idx<size){ idx+=blockSize; setTimeout(run);//尽量避免卡ui }else{ True(memory.buffer.slice(0,mOffset),"audio/ogg"); }; }; run(); } //********边录边转码(Worker)支持函数,如果提供就代表可能支持,否则只支持标准转码********* //全局共享一个Worker,后台串行执行。如果每次都开一个新的,编码速度可能会慢很多,可能是浏览器运行缓存的因素,并且可能瞬间产生多个并行操作占用大量cpu var oggWorker; Recorder.BindDestroy("oggWorker",function(){ if(oggWorker){ Recorder.CLog("oggWorker Destroy"); oggWorker.terminate(); oggWorker=null; }; }); Recorder.prototype.ogg_envCheck=function(envInfo,set){//检查环境下配置是否可用 var errMsg=""; //需要实时编码返回数据,此时需要检查是否可实时编码 if(set.takeoffEncodeChunk){ if(!newContext()){//浏览器不能创建实时编码环境 errMsg=$T("5si6::当前浏览器版本太低,无法实时处理"); }; }; if(!errMsg && !Recorder.OggVorbisEncoder){ errMsg=ImportEngineErr(); }; return errMsg; }; Recorder.prototype.ogg_start=function(set){//如果返回null代表不支持 return newContext(set); }; var openList={id:0}; var newContext=function(setOrNull,_badW){ //独立运行的函数,scope.wkScope worker.onmessage 字符串会被替换 var run=function(e){ var ed=e.data; var wk_ctxs=scope.wkScope.wk_ctxs; var wk_OggEnc=scope.wkScope.wk_OggEnc; var cur=wk_ctxs[ed.id]; if(ed.action=="init"){ wk_ctxs[ed.id]={ takeoff:ed.takeoff ,memory:new Uint8Array(500000), mOffset:0 ,encObj:new wk_OggEnc(ed.sampleRate, 1, ed.bitVv) }; }else if(!cur){ return; }; var addBytes=function(buf){ var bufLen=buf.length; if(cur.mOffset+bufLen>cur.memory.length){ var tmp=new Uint8Array(cur.memory.length+Math.max(500000,bufLen)); tmp.set(cur.memory.subarray(0, cur.mOffset)); cur.memory=tmp; } cur.memory.set(buf,cur.mOffset); cur.mOffset+=bufLen; }; switch(ed.action){ case "stop": if(!cur.isCp) try{ cur.encObj.flush() }catch(e){ console.error(e) } cur.encObj=null; delete wk_ctxs[ed.id]; break; case "encode": if(cur.isCp)break; try{ var buf=cur.encObj.encode([ed.pcm]); }catch(e){ //精简代码调用了abort cur.err=e; console.error(e); }; if(buf && buf.length>0){ if(cur.takeoff){ worker.onmessage({action:"takeoff",id:ed.id,chunk:buf}); }else{ addBytes(buf); }; }; break; case "complete": cur.isCp=1; try{ var buf=cur.encObj.flush(); }catch(e){ //精简代码调用了abort cur.err=e; console.error(e); }; if(buf && buf.length>0){ if(cur.takeoff){ worker.onmessage({action:"takeoff",id:ed.id,chunk:buf}); }else{ addBytes(buf); }; }; if(cur.err){ worker.onmessage({action:ed.action,id:ed.id ,err:"Ogg Encoder: "+cur.err.message}); break; }; worker.onmessage({ action:ed.action ,id:ed.id ,blob:cur.memory.buffer.slice(0,cur.mOffset) }); break; }; }; var initOnMsg=function(isW){ worker.onmessage=function(e){ var data=e; if(isW)data=e.data; var ctx=openList[data.id]; if(ctx){ if(data.action=="takeoff"){ //取走实时生成的ogg数据 ctx.set.takeoffEncodeChunk(new Uint8Array(data.chunk.buffer)); }else{ //complete ctx.call&&ctx.call(data); ctx.call=null; }; }; }; }; var initCtx=function(){ var ctx={worker:worker,set:setOrNull}; if(setOrNull){ ctx.id=++openList.id; openList[ctx.id]=ctx; var bitV=GetBitRate(setOrNull.bitRate); setOrNull.bitRate=bitV.bitRate; var takeoff=!!setOrNull.takeoffEncodeChunk; if(takeoff){ Recorder.CLog($T("R8yz::takeoffEncodeChunk接管OggVorbis编码器输出的二进制数据,Ogg由数据页组成,一页包含多帧音频数据(含几秒的音频,一页数据无法单独解码和播放),此编码器每次输出都是完整的一页数据,因此实时性会比较低;在合并成完整ogg文件时,必须将输出的所有数据合并到一起,否则可能无法播放,不支持截取中间一部分单独解码和播放"),3); }; worker.postMessage({ action:"init" ,id:ctx.id ,sampleRate:setOrNull.sampleRate ,bitRate:setOrNull.bitRate, bitVv:bitV.val ,takeoff:takeoff ,x:new Int16Array(5)//低版本浏览器不支持序列化TypedArray }); }else{ worker.postMessage({ x:new Int16Array(5)//低版本浏览器不支持序列化TypedArray }); }; return ctx; }; var scope,worker=oggWorker; //非浏览器,不支持worker,或者开启失败,使用UI线程处理 if(_badW || !HasWebWorker){ Recorder.CLog($T("hB9D::当前环境不支持Web Worker,OggVorbis实时编码器运行在主线程中"),3); worker={ postMessage:function(ed){ run({data:ed}); } }; scope={wkScope:{ wk_ctxs:{}, wk_OggEnc:Recorder.OggVorbisEncoder }}; initOnMsg(); return initCtx(); }; try{ if(!worker){ //创建一个新Worker var onmsg=(run+"").replace(/[\w\$]+\.onmessage/g,"self.postMessage"); onmsg=onmsg.replace(/[\w\$]+\.wkScope/g,"wkScope"); var jsCode=");self.onmessage="+onmsg; jsCode+=";var wkScope={ wk_ctxs:{},wk_OggEnc:Create() };"; if(Recorder.OggVorbisEncoder.Module.StaticSeed){ jsCode+="wkScope.wk_OggEnc.Module.StaticSeed=true;"; }; var engineCode=Recorder.OggVorbisEncoder.Create.toString(); var url=(window.URL||webkitURL).createObjectURL(new Blob(["var Create=(",engineCode,jsCode], {type:"text/javascript"})); worker=new Worker(url); setTimeout(function(){ (window.URL||webkitURL).revokeObjectURL(url);//必须要释放,不然每次调用内存都明显泄露内存 },10000);//chrome 83 file协议下如果直接释放,将会使WebWorker无法启动 initOnMsg(1); }; var ctx=initCtx(); ctx.isW=1; oggWorker=worker; return ctx; }catch(e){//出错了就不要提供了 worker&&worker.terminate(); console.error(e); return newContext(setOrNull, 1);//切换到UI线程处理 }; }; Recorder.prototype.ogg_stop=function(startCtx){ if(startCtx&&startCtx.worker){ startCtx.worker.postMessage({ action:"stop" ,id:startCtx.id }); startCtx.worker=null; delete openList[startCtx.id]; //疑似泄露检测 排除id var opens=-1; for(var k in openList){ opens++; }; if(opens){ Recorder.CLog($T("oTiy::ogg worker剩{1}个未stop",0,opens),3); }; }; }; Recorder.prototype.ogg_encode=function(startCtx,pcm){ if(startCtx&&startCtx.worker){ startCtx.worker.postMessage({ action:"encode" ,id:startCtx.id ,pcm:pcm }); }; }; Recorder.prototype.ogg_complete=function(startCtx,True,False,autoStop){ var This=this; if(startCtx&&startCtx.worker){ startCtx.call=function(data){ if(autoStop){ This.ogg_stop(startCtx); }; if(data.err){ False(data.err); }else{ True(data.blob,"audio/ogg"); }; }; startCtx.worker.postMessage({ action:"complete" ,id:startCtx.id }); }else{ False($T("dIpw::ogg编码器未start")); }; }; /* 编码输出测试,半天才输出一段数据 var ogg = new Recorder.OggVorbisEncoder(16000, 1, -0.1); for(var i=0;i<10*10;i++){v=ogg.encode([new Array(1600).fill().map(a=>~~(Math.random()*0x7fff))]).length; if(v)console.log(i,v)} console.log("flush");ogg.flush().length; */ //转换比特率成质量数值 var GetBitRate=function(bitRate){ bitRate=Math.min(Math.max(bitRate,16),100); //取值-0.1-1,实际输出16-100kbps var val=Math.max(1.1*(bitRate-16)/(100-16)-0.1, -0.1); return {bitRate:bitRate,val:val}; }; }));