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
JavaScript
/*
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){
;
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};
};
}));