HTML5录音控件
最近的項(xiàng)目又需要用到錄音,年前有過(guò)調(diào)研,再次翻出來(lái)使用,這里做一個(gè)記錄。
HTML5提供了錄音支持,因此可以方便使用HTML5來(lái)錄音,來(lái)實(shí)現(xiàn)錄音、語(yǔ)音識(shí)別等功能,語(yǔ)音開發(fā)必備。但是ES標(biāo)準(zhǔn)提供的API并不人性化,不方便使用,并且不提供保存為wav的功能,開發(fā)起來(lái)費(fèi)勁啊!!
github尋找輪子,發(fā)現(xiàn)Recorder.js,基本上可以滿足需求了,良好的封裝,支持導(dǎo)出wav,但是存在:
- wav采樣率不可調(diào)整
- recorder創(chuàng)建麻煩,需要自己初始化getUserMedia
- 無(wú)實(shí)時(shí)數(shù)據(jù)回調(diào),不方便繪制波形
- 。。。
改造輪子
創(chuàng)建recorder工具方法
提供創(chuàng)建recorder工具函數(shù),封裝audio接口:
static createRecorder(callback,config){window.AudioContext = window.AudioContext || window.webkitAudioContext;window.URL = window.URL || window.webkitURL;navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;if (navigator.getUserMedia) {navigator.getUserMedia({ audio: true } //只啟用音頻, function (stream) {var audio_context = new AudioContext;var input = audio_context.createMediaStreamSource(stream);var rec = new Recorder(input, config);callback(rec);}, function (error) {switch (error.code || error.name) {case 'PERMISSION_DENIED':case 'PermissionDeniedError':throwError('用戶拒絕提供信息。');break;case 'NOT_SUPPORTED_ERROR':case 'NotSupportedError':throwError('瀏覽器不支持硬件設(shè)備。');break;case 'MANDATORY_UNSATISFIED_ERROR':case 'MandatoryUnsatisfiedError':throwError('無(wú)法發(fā)現(xiàn)指定的硬件設(shè)備。');break;default:throwError('無(wú)法打開麥克風(fēng)。異常信息:' + (error.code || error.name));break;}});} else {throwError('當(dāng)前瀏覽器不支持錄音功能。'); return;}}采樣率
H5錄制的默認(rèn)是44k的,文件大,不方便傳輸,因此需要進(jìn)行重新采樣,一般采用插值取點(diǎn)方法:
以下代碼主要來(lái)自stackoverflow:
/*** 轉(zhuǎn)換采樣率* @param data* @param newSampleRate 目標(biāo)采樣率* @param oldSampleRate 原始數(shù)據(jù)采樣率* @returns {any[]|Array}*/function interpolateArray(data, newSampleRate, oldSampleRate) {var fitCount = Math.round(data.length * (newSampleRate / oldSampleRate));var newData = new Array();var springFactor = new Number((data.length - 1) / (fitCount - 1));newData[0] = data[0]; // for new allocationfor (var i = 1; i < fitCount - 1; i++) {var tmp = i * springFactor;var before = new Number(Math.floor(tmp)).toFixed();var after = new Number(Math.ceil(tmp)).toFixed();var atPoint = tmp - before;newData[i] = this.linearInterpolate(data[before], data[after], atPoint);}newData[fitCount - 1] = data[data.length - 1]; // for new allocationreturn newData;}function linearInterpolate(before, after, atPoint) {return before + (after - before) * atPoint;}修改導(dǎo)出wav函數(shù)exportWAV,增加采樣率選項(xiàng):
/*** 導(dǎo)出wav* @param type* @param desiredSamplingRate 期望的采樣率*/function exportWAV(type,desiredSamplingRate) {// 默認(rèn)為16kdesiredSamplingRate = desiredSamplingRate || 16000;var buffers = [];for (var channel = 0; channel < numChannels; channel++) {var buffer = mergeBuffers(recBuffers[channel], recLength);// 需要轉(zhuǎn)換采樣率if (desiredSamplingRate!=sampleRate) {// 插值去點(diǎn)buffer = interpolateArray(buffer, desiredSamplingRate, sampleRate);}buffers.push(buffer);}var interleaved = numChannels === 2 ? interleave(buffers[0], buffers[1]) : buffers[0];var dataview = encodeWAV(interleaved,desiredSamplingRate);var audioBlob = new Blob([dataview], { type: type });self.postMessage({ command: 'exportWAV', data: audioBlob });}實(shí)時(shí)錄音數(shù)據(jù)回調(diào)
為了方便繪制音量、波形圖,需要獲取到實(shí)時(shí)數(shù)據(jù):
config新增一個(gè)回調(diào)函數(shù)onaudioprocess:
config = {bufferLen: 4096,numChannels: 1, // 默認(rèn)單聲道m(xù)imeType: 'audio/wav',onaudioprocess:null};修改錄音數(shù)據(jù)處理函數(shù):
this.node.onaudioprocess = (e) => {if (!this.recording) return;var buffer = [];for (var channel = 0; channel < this.config.numChannels; channel++) {buffer.push(e.inputBuffer.getChannelData(channel));}// 發(fā)送給workerthis.worker.postMessage({command: 'record',buffer: buffer});// 數(shù)據(jù)回調(diào)if(this.config.onaudioprocess){this.config.onaudioprocess(buffer[0]);}};這樣,在創(chuàng)建recorder時(shí),配置onaudioprocess就可以獲取到實(shí)時(shí)數(shù)據(jù)了
實(shí)時(shí)數(shù)據(jù)編碼
編碼計(jì)算耗時(shí),需要放到worker執(zhí)行:
接口函數(shù)新增encode,發(fā)送消息給worker,讓worker執(zhí)行:
encode(cb,buffer,sampleRate) {cb = cb || this.config.callback;if (!cb) throw new Error('Callback not set');this.callbacks.encode.push(cb);this.worker.postMessage({ command: 'encode',buffer:buffer,sampleRate:sampleRate});}worker里新增encode函數(shù),處理encode請(qǐng)求,完成后執(zhí)行回調(diào)
self.onmessage = function (e) {switch (e.data.command) {case 'encode':encode(e.data.buffer,e.data.sampleRate);break;}};encode(cb,buffer,sampleRate) {cb = cb || this.config.callback;if (!cb) throw new Error('Callback not set');this.callbacks.encode.push(cb);this.worker.postMessage({ command: 'encode',buffer:buffer,sampleRate:sampleRate});}wav上傳
增加一個(gè)上傳函數(shù):
exportWAVAndUpload(url, callback) {var _url = url;exportWAV(function(blob){var fd = new FormData();fd.append("audioData", blob);var xhr = new XMLHttpRequest();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);}) }完整代碼
=點(diǎn)擊下載
發(fā)現(xiàn)新輪子
今天再次看這個(gè)項(xiàng)目,發(fā)現(xiàn)這個(gè)項(xiàng)目已經(jīng)不維護(hù)了,
Note: This repository is not being actively maintained due to lack of time and interest. If you maintain or know of a good fork, please let me know so I can direct future visitors to it. In the meantime, if this library isn't working, you can find a list of popular forks here: http://forked.yannick.io/mattdiamond/recorderjs.
作者推薦https://github.com/chris-rudmin/Recorderjs,提供更多的功能:
- bitRate (optional) Specifies the target bitrate in bits/sec. The encoder selects an application-specific default when this is not specified.
- bufferLength - (optional) The length of the buffer that the internal JavaScriptNode uses to capture the audio. Can be tweaked if experiencing performance issues. Defaults to 4096.
- encoderApplication - (optional) Specifies the encoder application. Supported values are 2048 - Voice, 2049 - Full Band Audio, 2051 - Restricted Low Delay. Defaults to 2049.
- encoderComplexity - (optional) Value between 0 and 10 which determines latency and processing for resampling. 0 is fastest with lowest complexity. 10 is slowest with highest complexity. The encoder selects a default when this is not specified.
- encoderFrameSize (optional) Specifies the frame size in ms used for encoding. Defaults to 20.
- encoderPath - (optional) Path to encoderWorker.min.js worker script. Defaults to encoderWorker.min.js
- encoderSampleRate - (optional) Specifies the sample rate to encode at. Defaults to 48000. Supported values are 8000, 12000, 16000, 24000 or 48000.
- leaveStreamOpen - (optional) Keep the stream around when trying to stop recording, so you can re-start without re-initStream. Defaults to false.
- maxBuffersPerPage - (optional) Specifies the maximum number of buffers to use before generating an Ogg page. This can be used to lower the streaming latency. The lower the value the more overhead the ogg stream will incur. Defaults to 40.
- monitorGain - (optional) Sets the gain of the monitoring output. Gain is an a-weighted value between 0 and 1. Defaults to 0
- numberOfChannels - (optional) The number of channels to record. 1 = mono, 2 = stereo. Defaults to 1. Maximum 2 channels are supported.
- originalSampleRateOverride - (optional) Override the ogg opus 'input sample rate' field. Google Speech API requires this field to be 16000.
- resampleQuality - (optional) Value between 0 and 10 which determines latency and processing for resampling. 0 is fastest with lowest quality. 10 is slowest with highest quality. Defaults to 3.
- streamPages - (optional) dataAvailable event will fire after each encoded page. Defaults to false.
推薦使用
作者:Jadepeng
出處:jqpeng的技術(shù)記事本--http://www.cnblogs.com/xiaoqi
您的支持是對(duì)博主最大的鼓勵(lì),感謝您的認(rèn)真閱讀。
本文版權(quán)歸作者所有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
轉(zhuǎn)載于:https://www.cnblogs.com/xiaoqi/p/6993912.html
總結(jié)