录音实践课总结,h5实现录音
获取脉冲编码调制数据
处理脉冲编码调制数据
Float32转Int16
数组缓冲转Base64
脉冲编码调制文件播放
重采样
脉冲编码调制转MP3文件文件
脉冲编码调制转声音资源文件
短时能量计算
网络工作者优化性能
音频存储(索引数据库)
网络视图开启WebRTC
获取 PCM 数据
查看演示
https://github.com/deepkolos/pc-pcm-wave
样例代码:
常数媒体流=等待窗口。领航员。媒体设备。getuser media({ audio:{//采样率:44100,//采样率不生效需要手动重采样channelCount: 1,//声道//echoCancellation: true,//noiseSuppression: true,//降噪实测效果不错},})const audioContext=新窗口.audio context()const inputSampleRate=音频上下文。samplerateconst媒体节点=音频上下文。createmediastreamsource(mediaStream)if(!音频上下文。createscriptprocessor){音频上下文。createscriptprocessor=音频上下文。createjavascriptnode }//创建一个jsNodeconst jsNode=音频上下文。createscriptprocessor(4096,1,1)jsNode。连接(音频上下文。目的地)jsNode。onaudioprocess=(e)={//e .输入缓冲区。getchanneldata(0)(左)//双通道通过输入缓冲区。getChannelData(1)获取(右)}mediaNode.connect(jsNode)简要流程如下:
开始=开始:开始getUserMedia=operation:获取MediaStreamaudioContext=operation:创建AudioContextscriptNode=操作:创建scriptNode并关联AudioContextonaudioprocess=operation:设置音频处理并处理数据结束=结束:结束开始-获取用户媒体-音频上下文-脚本节点-音频处理-结束停止录制只需要把音频环境挂在的结节卸载即可,然后把存储的每一帧数据合并即可产出脉冲编码调制数据
jsnode。断开()medianode。disconnect()jsnode。onaudioprocess=nullPCM 数据处理
通过WebRTC获取的脉冲编码调制数据格式是Float32的,如果是双通道录音的话,还需要增加合并通道
const left datalist=[];const right datalist=[];函数onAudioProcess(event) { //一帧的音频脉冲编码调制数据让音频缓冲区=事件。输入缓冲器;左侧数据列表。按下(音频缓冲。getchanneldata(0)).slice(0));右侧数据列表。按下(音频缓冲。getchanneldata(1)).slice(0));}//交叉合并左右声道的数据函数interleaveLeftAndRight(left,right){ let总长度=left。长度正确。长度;let data=new float 32数组(总长度);对于(设I=0;我离开了我){设k=I * 2;data[k]=left[I];data[k 1]=right[I];}返回数据;}浮动32转Int16
const float 32=new float 32 array(1)const int 16=int 16 array。从(浮动32。map(x=(x ^ 0?x *0x7fff : x *0x8000)),)arrayBuffer 转 Base64
注意:在浏览器上有个btoa()函数也是可以转换为Base64但是输入参数必须为字符串,如果传递缓冲器参数会先被toString()然后再Base64,使用ffplay播放反序列化的Base64,会比较刺耳
使用base64-数组缓冲区即可完成
从“base64-array buffer”导入{ encode } const float 32=new float 32 array(1)const int 16=int 16 array。从(浮动32。map(x=(x ^ 0?x *0x7fff : x *0x8000)),)控制台。log(encode(int 16。缓冲剂))验证Base64是否正确,可以在结节下把产出的Base64转换为Int16的脉冲编码调制文件,然后使用FFPlay播放,看看音频是否正常播放
PCM 文件播放
# 单通道采样率:16000 int 16 ffplay-f s16le-ar 16k-AC 1测试。PCM #双通道采样率:48000浮空32f play-f f32le-ar 48000-AC 2测试。脉冲编码调制重采样/调整采样率
虽然getUserMedia参数可设置采样率,但是在最新铬也不生效,所以需要手动做个重采样
常数媒体流=等待窗口。领航员。媒体设备。getuser media({ audio:{//采样率:44100,//采样率设置不生效channelCount: 1,//声道//echoCancellation: true,//减低回音//noiseSuppression: true,//降噪,实测效果不错},})使用波形重采样器即可完成
从“波形重采样器”导入{重新采样}常量输入采样率=44100常量输出采样率=16000常量重新采样缓冲区=重新采样(//需要音频处理每一帧的缓冲器合并后的数组mergeArray(audioBuffers),inputSampleRate,outputSampleRate,)PCM转MP3文件文件
从“拉美人”导入{ MP 3编码器}设MP 3 buf const MP 3 data=[]const sample block size=576 * 10//工作缓存区, 576的倍数const MP 3 encoder=新的MP 3编码器(1,outputSampleRate,kbps)const samples=float 32 point 16(音频缓冲区,inputSampleRate,outputSampleRate,)let remaining=samples。长度为(设I=0;剩余=0;I=样本块大小){ const left=样本。子阵列(I,I样本块大小)MP 3 buf=MP 3编码器。编码缓冲区(左)MP 3数据。push(new int 8 array(MP 3 buf))剩余-=样本块大小} MP 3数据。推(新的int 8数组(MP 3编码器。flush()))控制台。日志(MP 3数据)//工具函数函数float 32 point 16(音频缓冲区,输入采样速率,输出采样速率){ const float 32=re采样(//需要音频处理每一帧的缓冲器合并后的数组mergeArray(audioBuffers),inputSampleRate,outputSampleRate,)const int 16=int 16 array。从(浮动32。map(x=(x ^ 0?x *0x7fff : x *0x8000)),)return int16}使用拉梅斯即可,但是体积较大(160 KB),如果没有存储需求可使用声音资源文件格式
ls -alh-rwxrwxrwx 1根根95K 4月22点12分45秒。MP3 *-rwxrwxrwx 1根根1.1M 4月22点12分44秒。wav *-rwxrwxrwx 1根根235k 4月22点12分41秒30秒。MP3 *-rwxrwxrwx 1根根2.6M 4月22点40分30秒。wav *-rwxrwxrwx 1根根63K 4月22点12分49秒8秒。MP3 *-rwxrwxrwx 1根根689k 4月22 12:48 8s.wav*PCM转声音资源文件
函数merge array(list){ const length=list。长度*列表[0].length const data=new float 32 array(length)let offset=0 for(let I=0;I list . length I){ data . set(list[I],offset) offset=list[i].长度}返回数据}函数writeUTFBytes(view,offset,string){ var LNG=string。长度为(设I=0;我液化天然气;i ) { view.setUint8(偏移量I,字符串。charcode at(I))} }函数createWavBuffer(audioData,sampleRate=44100,channels=1){ const WAV _ HEAD _ SIZE=44 const buffer=新数组缓冲区(audioData。length * 2 WAV _ HEAD _ SIZE)//需要用一个视角来操控缓冲区常量视图=新数据视图(缓冲区)//写入声音资源文件头部信息//RIFF块描述符/标识符writeUTFBytes(view,0, RIFF) //RIFF块长度view.setUint32(4,44 audioData.length * 2,true) //RIFF类型writeUTFBytes(view,8, WAVE) //格式块标识符//FMT子块writeUTFBytes(view,12, fmt) //格式块长度view.setUint32(16,16,true) //样本格式(raw) view.setUint16(20,1,true) //立体声(2声道)view.setUint16(22,channels,true) //采样率view.setUint32(24,采样率,真)//字节率(采样率*块对齐)view.setUint32(28,sampleRate * 2,true) //块对齐(声道数*每采样字节数)view.setUint16(32个通道* 2,true) //每采样位数view.setUint16(34,16,true) //数据子块//子块写入脉冲编码调制数据设index=44 const volume=1 const { length }=用于(设I=0;我长度;i ) { view.setInt16(index,audioData[i] * (0x7fff * volume),true) index=2 }返回缓冲区}//需要音频处理每一帧的缓冲器合并后的数组createWavBuffer(合并数组(音频缓冲区))基本上是脉冲编码调制加上一些音频信息
简单的短时能量计算
函数短时能量(audioData){ let sum=0 const energy=[]const { length }=audioData for(let I=0;我长度;I){ sum=audio data[I]* * 2 if((I 1)% 256===0){ energy。推(sum)sum=0 } else if(I===length-1){ energy。push(sum)} }返回能量}由于计算结果有会因设备的录音增益差异较大,计算出数据也较大,所以使用比值简单区分人声和噪音
查看演示
恒定噪声语音分水岭波=2.3恒定能量=短时能量(例如输入缓冲。getchanneldata(0).slice(0))const avg=能量。减少((a,b)=a b)/能量。长度常数下一状态=数学。最大(.能量)/avg噪音语音分水岭波?声音:噪音Web Worker 优化性能
音频数据数据量较大,所以可以使用网络工作者进行优化,不卡用户界面线程
在网络包项目里网络工作者比较简单,安装工人装载机即可
preact.config.js
导出默认值(config,env,helpers)={ config。模块。规则。推({ test:/\ .工人\。js$/,使用:{装载机:工人装载机,选项:{ inline: true } },})}recorder.worker.js
self.addEventListener(message ,event={ console。日志(事件。数据)//转MP3/转Base64/转声音资源文件等等const output= self。后期消息(输出)}使用工人
异步函数toMP3(audioBuffers,inputSampleRate,outputSampleRate=16000){ const { default:Worker }=await import( ./录音机。Worker )const Worker=new Worker()//简单使用,项目可以在记录员实例化的时候创建工人实例,有并法需求可多个实例返回新的承诺(resolve={ worker。post message({ audio buffers:audio buffers,inputSampleRate:inputSampleRate,outputSampleRate:outputSampleRate,type: mp3 ,})worker。on消息=事件=解决(事件。数据)})音频的存储
浏览器持久化储存的地方有本地存储和IndexedDB,其中本地存储较为常用,但是只能储存字符串,而索引数据库可直接储存斑点,所以优先选择IndexedDB,使用本地存储则需要转Base64体积将会更大
所以为了避免占用用户太多空间,所以选择MP3文件文件格式进行存储
ls -alh-rwxrwxrwx 1根根95K 4月22点12分45秒。MP3 *-rwxrwxrwx 1根根1.1M 4月22点12分44秒。wav *-rwxrwxrwx 1根根235k 4月22点12分41秒30秒。MP3 *-rwxrwxrwx 1根根2.6M 4月22点40分30秒。wav *-rwxrwxrwx 1根根63K 4月22点12分49秒8秒。MP3 *-rwxrwxrwx 1根根689k 4月22 12:48 8s.wav*IndexedDB简单封装如下,熟悉后台的同学可以找个对象关系映射(对象关系映射)库方便数据读写
常数索引数据库=窗口。索引数据库 窗口。webkitindexeddb window。moziindexeddb 窗口.OIndexedDB 窗口。msindexeddbconst IDB transaction=window .IDBTransaction 窗口。webkitidbtransaction 窗口.OIDBTransaction 窗口。msidbtransactionconst读写模式=IDB事务得类型.READ_WRITE===未定义? readwrite : IDBTransaction .READ _ write const db version=1 const store default= MP3 let db link函数initDB(store){ return new Promise((resolve,reject)={ if(db link)resolve(db link)//创建/打开数据库const request=索引数据库。打开(“音频”,数据库版本)请求。成功时=event={ const db=request。结果数据库。on error=event={ reject(event)} if(db。版本===数据库版本)解析(数据库)}请求。on=event={ reject(事件目前仅在最新的火狐浏览器版本中请求。onupgradereneed=event={ db link=event。目标。结果常数{事务}=事件。目标if(!数据库链接。objectstorenames。包含(存储)){数据库链接。createobjectstore(store)}事务。on complete=event={//Now store可用于填充resolve(db link)} } } export const write IDB=async(name,blob,store=store default)={ const db=await initDB(store)const transaction=db。transaction([store],读写模式)const objStore=transaction。objectstore(store)return new Promise((resolve,reject)={ const request=objStore。put store=store default)={ const db=await initDB(store)const transaction=db。transaction([store],读写模式)const objStore=transaction。objectstore(store)return new Promise((resolve,reject)={ const request=objStore。获取(名称)请求。成功时=事件=解决(事件。目标。结果)请求。on error=event=reject(事件)事务。commit()} } export const clear IDB=async(存储默认值)={ const db=await init
见WebView WebRTC不工作
网络视图。setwebchromeclient(new WebChromeClient(){ @ target API(Build .版本代码. LOLLIPOP)@ override public void onPermissionRequest(最终权限请求请求){请求。授予(请求。获取资源());}});到此这篇关于HTML5录音实践总结(预动作)的文章就介绍到这了,更多相关html5录音内容请搜索以前的文章或继续浏览下面的相关文章,希望大家以后多多支持!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。