html5录音功能如何实现
这篇文章主要介绍“html5录音功能如何实现”,在日常操作中,相信很多人在html5录音功能如何实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”html5录音功能如何实现”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
缘起
由于项目需要,我们要在web端实现录音功能。一开始,找到的方案有两个,一个是通过iframe,一个是html5的getUserMedia api。由于我们的录音功能不需要兼容IE浏览器,所以毫不犹豫的选择了html5提供的getUserMedia去实现。基本思路是参考了官方的api文档以及网上查找的一些方案做结合做出了适合项目需要的方案。但由于我们必须保证这个录音功能能够同时在pad端、pc端都可以打开,所以其中也踩了一些坑。以下为过程还原。
步骤1
由于新的api是通过navigator.mediaDevices.getUserMedia,且返回一个promise。
而旧的api是navigator.getUserMedia,于是做了一个兼容性。代码如下:
//老的浏览器可能根本没有实现mediaDevices,所以我们可以先设置一个空的对象if(navigator.mediaDevices===undefined){navigator.mediaDevices={};}//一些浏览器部分支持mediaDevices。我们不能直接给对象设置getUserMedia//因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。if(navigator.mediaDevices.getUserMedia===undefined){letgetUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia;navigator.mediaDevices.getUserMedia=function(constraints){//首先,如果有getUserMedia的话,就获得它//一些浏览器根本没实现它-那么就返回一个error到promise的reject来保持一个统一的接口if(!getUserMedia){returnPromise.reject(newError('getUserMediaisnotimplementedinthisbrowser'));}//否则,为老的navigator.getUserMedia方法包裹一个PromisereturnnewPromise(function(resolve,reject){getUserMedia.call(navigator,constraints,resolve,reject);});};
步骤2
这是网上存在的一个方法,封装了一个HZRecorder。基本上引用了这个方法。调用HZRecorder.get就可以调起录音接口,这个方法传入一个callback函数,new HZRecorder后执行callback函数且传入一个实体化后的HZRecorder对象。可以通过该对象的方法实现开始录音、暂停、停止、播放等功能。
varHZRecorder=function(stream,config){config=config||{};config.sampleBits=config.sampleBits||8;//采样数位8,16config.sampleRate=config.sampleRate||(44100/6);//采样率(1/644100)//创建一个音频环境对象audioContext=window.AudioContext||window.webkitAudioContext;varcontext=newaudioContext();//将声音输入这个对像varaudioInput=context.createMediaStreamSource(stream);//设置音量节点varvolume=context.createGain();audioInput.connect(volume);//创建缓存,用来缓存声音varbufferSize=4096;//创建声音的缓存节点,createScriptProcessor方法的//第二个和第三个参数指的是输入和输出都是双声道。varrecorder=context.createScriptProcessor(bufferSize,2,2);varaudioData={size:0//录音文件长度,buffer:[]//录音缓存,inputSampleRate:context.sampleRate//输入采样率,inputSampleBits:16//输入采样数位8,16,outputSampleRate:config.sampleRate//输出采样率,oututSampleBits:config.sampleBits//输出采样数位8,16,input:function(data){this.buffer.push(newFloat32Array(data));this.size+=data.length;},compress:function(){//合并压缩//合并vardata=newFloat32Array(this.size);varoffset=0;for(vari=0;i<this.buffer.length;i++){data.set(this.buffer[i],offset);offset+=this.buffer[i].length;}//压缩varcompression=parseInt(this.inputSampleRate/this.outputSampleRate);varlength=data.length/compression;varresult=newFloat32Array(length);varindex=0,j=0;while(index<length){result[index]=data[j];j+=compression;index++;}returnresult;},encodeWAV:function(){varsampleRate=Math.min(this.inputSampleRate,this.outputSampleRate);varsampleBits=Math.min(this.inputSampleBits,this.oututSampleBits);varbytes=this.compress();vardataLength=bytes.length*(sampleBits/8);varbuffer=newArrayBuffer(44+dataLength);vardata=newDataView(buffer);varchannelCount=1;//单声道varoffset=0;varwriteString=function(str){for(vari=0;i<str.length;i++){data.setUint8(offset+i,str.charCodeAt(i));}};//资源交换文件标识符writeString('RIFF');offset+=4;//下个地址开始到文件尾总字节数,即文件大小-8data.setUint32(offset,36+dataLength,true);offset+=4;//WAV文件标志writeString('WAVE');offset+=4;//波形格式标志writeString('fmt');offset+=4;//过滤字节,一般为0x10=16data.setUint32(offset,16,true);offset+=4;//格式类别(PCM形式采样数据)data.setUint16(offset,1,true);offset+=2;//通道数data.setUint16(offset,channelCount,true);offset+=2;//采样率,每秒样本数,表示每个通道的播放速度data.setUint32(offset,sampleRate,true);offset+=4;//波形数据传输率(每秒平均字节数)单声道×每秒数据位数×每样本数据位/8data.setUint32(offset,channelCount*sampleRate*(sampleBits/8),true);offset+=4;//快数据调整数采样一次占用字节数单声道×每样本的数据位数/8data.setUint16(offset,channelCount*(sampleBits/8),true);offset+=2;//每样本数据位数data.setUint16(offset,sampleBits,true);offset+=2;//数据标识符writeString('data');offset+=4;//采样数据总数,即数据总大小-44data.setUint32(offset,dataLength,true);offset+=4;//写入采样数据if(sampleBits===8){for(vari=0;i<bytes.length;i++,offset++){vars=Math.max(-1,Math.min(1,bytes[i]));varval=s<0?s*0x8000:s*0x7FFF;val=parseInt(255/(65535/(val+32768)));data.setInt8(offset,val,true);}}else{for(vari=0;i<bytes.length;i++,offset+=2){vars=Math.max(-1,Math.min(1,bytes[i]));data.setInt16(offset,s<0?s*0x8000:s*0x7FFF,true);}}returnnewBlob([data],{type:'audio/wav'});}};//开始录音this.start=function(){audioInput.connect(recorder);recorder.connect(context.destination);};//停止this.stop=function(){recorder.disconnect();};//结束this.end=function(){context.close();};//继续this.again=function(){recorder.connect(context.destination);};//获取音频文件this.getBlob=function(){this.stop();returnaudioData.encodeWAV();};//回放this.play=function(audio){audio.src=window.URL.createObjectURL(this.getBlob());};//上传this.upload=function(url,callback){varfd=newFormData();fd.append('audioData',this.getBlob());varxhr=newXMLHttpRequest();if(callback){xhr.upload.addEventListener('progress',function(e){callback('uploading',e);},false);xhr.addEventListener('load',function(e){callback('ok',e);},false);xhr.addEventListener('error',function(e){callback('error',e);},false);xhr.addEventListener('abort',function(e){callback('cancel',e);},false);}xhr.open('POST',url);xhr.send(fd);};//音频采集recorder.onaudioprocess=function(e){audioData.input(e.inputBuffer.getChannelData(0));//record(e.inputBuffer.getChannelData(0));};};//抛出异常HZRecorder.throwError=function(message){thrownewfunction(){this.toString=function(){returnmessage;};};};//是否支持录音HZRecorder.canRecording=(navigator.getUserMedia!=null);//获取录音机HZRecorder.get=function(callback,config){if(callback){navigator.mediaDevices.getUserMedia({audio:true}).then(function(stream){letrec=newHZRecorder(stream,config);callback(rec);}).catch(function(error){HZRecorder.throwError('无法录音,请检查设备状态');});}};window.HZRecorder=HZRecorder;
以上,已经可以满足大部分的需求。但是我们要兼容pad端。我们的pad有几个问题必须解决。
录音格式必须是mp3才能播放
window.URL.createObjectURL传入blob数据在pad端报错,转不了
以下为解决这两个问题的方案。
步骤3
以下为我实现 录音格式为mp3 和 window.URL.createObjectURL传入blob数据在pad端报错 的方案。
1、修改HZRecorder里的audioData对象代码。并引入网上一位大神的一个js文件lamejs.js
constlame=newlamejs();letaudioData={samplesMono:null,maxSamples:1152,mp3Encoder:newlame.Mp3Encoder(1,context.sampleRate||44100,config.bitRate||128),dataBuffer:[],size:0,//录音文件长度buffer:[],//录音缓存inputSampleRate:context.sampleRate,//输入采样率inputSampleBits:16,//输入采样数位8,16outputSampleRate:config.sampleRate,//输出采样率oututSampleBits:config.sampleBits,//输出采样数位8,16convertBuffer:function(arrayBuffer){letdata=newFloat32Array(arrayBuffer);letout=newInt16Array(arrayBuffer.length);this.floatTo16BitPCM(data,out);returnout;},floatTo16BitPCM:function(input,output){for(leti=0;i<input.length;i++){lets=Math.max(-1,Math.min(1,input[i]));output[i]=s<0?s*0x8000:s*0x7fff;}},appendToBuffer:function(mp3Buf){this.dataBuffer.push(newInt8Array(mp3Buf));},encode:function(arrayBuffer){this.samplesMono=this.convertBuffer(arrayBuffer);letremaining=this.samplesMono.length;for(leti=0;remaining>=0;i+=this.maxSamples){letleft=this.samplesMono.subarray(i,i+this.maxSamples);letmp3buf=this.mp3Encoder.encodeBuffer(left);this.appendToBuffer(mp3buf);remaining-=this.maxSamples;}},finish:function(){this.appendToBuffer(this.mp3Encoder.flush());returnnewBlob(this.dataBuffer,{type:'audio/mp3'});},input:function(data){this.buffer.push(newFloat32Array(data));this.size+=data.length;},compress:function(){//合并压缩//合并letdata=newFloat32Array(this.size);letoffset=0;for(leti=0;i<this.buffer.length;i++){data.set(this.buffer[i],offset);offset+=this.buffer[i].length;}//压缩letcompression=parseInt(this.inputSampleRate/this.outputSampleRate,10);letlength=data.length/compression;letresult=newFloat32Array(length);letindex=0;letj=0;while(index<length){result[index]=data[j];j+=compression;index++;}returnresult;},encodeWAV:function(){letsampleRate=Math.min(this.inputSampleRate,this.outputSampleRate);letsampleBits=Math.min(this.inputSampleBits,this.oututSampleBits);letbytes=this.compress();letdataLength=bytes.length*(sampleBits/8);letbuffer=newArrayBuffer(44+dataLength);letdata=newDataView(buffer);letchannelCount=1;//单声道letoffset=0;letwriteString=function(str){for(leti=0;i<str.length;i++){data.setUint8(offset+i,str.charCodeAt(i));}};//资源交换文件标识符writeString('RIFF');offset+=4;//下个地址开始到文件尾总字节数,即文件大小-8data.setUint32(offset,36+dataLength,true);offset+=4;//WAV文件标志writeString('WAVE');offset+=4;//波形格式标志writeString('fmt');offset+=4;//过滤字节,一般为0x10=16data.setUint32(offset,16,true);offset+=4;//格式类别(PCM形式采样数据)data.setUint16(offset,1,true);offset+=2;//通道数data.setUint16(offset,channelCount,true);offset+=2;//采样率,每秒样本数,表示每个通道的播放速度data.setUint32(offset,sampleRate,true);offset+=4;//波形数据传输率(每秒平均字节数)单声道×每秒数据位数×每样本数据位/8data.setUint32(offset,channelCount*sampleRate*(sampleBits/8),true);offset+=4;//快数据调整数采样一次占用字节数单声道×每样本的数据位数/8data.setUint16(offset,channelCount*(sampleBits/8),true);offset+=2;//每样本数据位数data.setUint16(offset,sampleBits,true);offset+=2;//数据标识符writeString('data');offset+=4;//采样数据总数,即数据总大小-44data.setUint32(offset,dataLength,true);offset+=4;//写入采样数据if(sampleBits===8){for(leti=0;i<bytes.length;i++,offset++){consts=Math.max(-1,Math.min(1,bytes[i]));letval=s<0?s*0x8000:s*0x7fff;val=parseInt(255/(65535/(val+32768)),10);data.setInt8(offset,val,true);}}else{for(leti=0;i<bytes.length;i++,offset+=2){consts=Math.max(-1,Math.min(1,bytes[i]));data.setInt16(offset,s<0?s*0x8000:s*0x7fff,true);}}returnnewBlob([data],{type:'audio/wav'});}};
2、修改HZRecord的音频采集的调用方法。
//音频采集recorder.onaudioprocess=function(e){audioData.encode(e.inputBuffer.getChannelData(0));};
3、HZRecord的getBlob方法。
this.getBlob=function(){this.stop();returnaudioData.finish();};
4、HZRecord的play方法。把blob转base64url。
this.play=function(func){readBlobAsDataURL(this.getBlob(),func);};functionreadBlobAsDataURL(data,callback){letfileReader=newFileReader();fileReader.onload=function(e){callback(e.target.result);};fileReader.readAsDataURL(data);}
至此,已经解决以上两个问题。
步骤4
这里主要介绍怎么做录音时的动效。
根据传入的音量大小,做一个圆弧动态扩展。
//创建analyser节点,获取音频时间和频率数据constanalyser=context.createAnalyser();audioInput.connect(analyser);constinputAnalyser=newUint8Array(1);constwrapEle=$this.refs['wrap'];letctx=wrapEle.getContext('2d');constwidth=wrapEle.width;constheight=wrapEle.height;constcenter={x:width/2,y:height/2};functiondrawArc(ctx,color,x,y,radius,beginAngle,endAngle){ctx.beginPath();ctx.lineWidth=1;ctx.strokeStyle=color;ctx.arc(x,y,radius,(Math.PI*beginAngle)/180,(Math.PI*endAngle)/180);ctx.stroke();}(functiondrawSpectrum(){analyser.getByteFrequencyData(inputAnalyser);//获取频域数据ctx.clearRect(0,0,width,height);//画线条for(leti=0;i<1;i++){letvalue=inputAnalyser[i]/3;//<===获取数据letcolors=[];if(value<=16){colors=['#f5A631','#f5A631','#e4e4e4','#e4e4e4','#e4e4e4','#e4e4e4'];}elseif(value<=32){colors=['#f5A631','#f5A631','#f5A631','#f5A631','#e4e4e4','#e4e4e4'];}else{colors=['#f5A631','#f5A631','#f5A631','#f5A631','#f5A631','#f5A631'];}drawArc(ctx,colors[0],center.x,center.y,52+16,-30,30);drawArc(ctx,colors[1],center.x,center.y,52+16,150,210);drawArc(ctx,colors[2],center.x,center.y,52+32,-22.5,22.5);drawArc(ctx,colors[3],center.x,center.y,52+32,157.5,202.5);drawArc(ctx,colors[4],center.x,center.y,52+48,-13,13);drawArc(ctx,colors[5],center.x,center.y,52+48,167,193);}//请求下一帧requestAnimationFrame(drawSpectrum);})();
到此,关于“html5录音功能如何实现”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。